Merge tag 'android-15.0.0_r32' of https://android.googlesource.com/platform/frameworks/opt/telephony into HEAD

Android 15.0.0 Release 32 (BP1A.250505.005)

Change-Id: I5fa8fefec97255eedbdb391053df948c51b7a5d3

# -----BEGIN PGP SIGNATURE-----
#
# iF0EABECAB0WIQRDQNE1cO+UXoOBCWTorT+BmrEOeAUCaBqG7gAKCRDorT+BmrEO
# eLCZAJ4mWmZQOhu11/0Pwv6ShBFZnMd/1gCdHMdluo+u5OnsSY2FcTTy+TV/EaU=
# =N2Tk
# -----END PGP SIGNATURE-----
# gpg: Signature faite le mar 06 mai 2025 18:02:22 EDT
# gpg:                avec la clef DSA 4340D13570EF945E83810964E8AD3F819AB10E78
# gpg: Impossible de vérifier la signature : Pas de clef publique
diff --git a/Android.bp b/Android.bp
index c3b4373..a6e526c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -82,14 +82,14 @@
         "android.hardware.radio-V1.4-java",
         "android.hardware.radio-V1.5-java",
         "android.hardware.radio-V1.6-java",
-        "android.hardware.radio.config-V3-java",
-        "android.hardware.radio.data-V3-java",
-        "android.hardware.radio.ims-V2-java",
-        "android.hardware.radio.messaging-V3-java",
-        "android.hardware.radio.modem-V3-java",
-        "android.hardware.radio.network-V3-java",
-        "android.hardware.radio.sim-V3-java",
-        "android.hardware.radio.voice-V3-java",
+        "android.hardware.radio.config-V4-java",
+        "android.hardware.radio.data-V4-java",
+        "android.hardware.radio.ims-V3-java",
+        "android.hardware.radio.messaging-V4-java",
+        "android.hardware.radio.modem-V4-java",
+        "android.hardware.radio.network-V4-java",
+        "android.hardware.radio.sim-V4-java",
+        "android.hardware.radio.voice-V4-java",
         "voip-common",
         "ims-common",
         "unsupportedappusage",
@@ -116,4 +116,7 @@
         obfuscate: false,
         proguard_flags_files: ["proguard.flags"],
     },
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/flags/Android.bp b/flags/Android.bp
index 1885032..edcfc3f 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -33,6 +33,7 @@
         "subscription.aconfig",
         "uicc.aconfig",
         "satellite.aconfig",
-        "iwlan.aconfig"
+        "iwlan.aconfig",
+        "carrier.aconfig",
     ],
 }
diff --git a/flags/calling.aconfig b/flags/calling.aconfig
index a5757d8..27d3518 100644
--- a/flags/calling.aconfig
+++ b/flags/calling.aconfig
@@ -62,3 +62,23 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+# OWNER=yongnamcha TARGET=25Q2
+flag {
+    name: "emergency_callback_mode_notification"
+    namespace: "telephony"
+    description: "Used to notify the emergency callback mode for call/SMS to other applications."
+    bug:"359064059"
+    is_exported: true
+}
+
+# OWNER=sewookseo TARGET=25Q2
+flag {
+    name: "pass_copied_call_state_list"
+    namespace: "telephony"
+    description: "To prevent passing the TelephonyRegistry's original instance to listeners in the same process"
+    bug:"379126049"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
\ No newline at end of file
diff --git a/flags/carrier.aconfig b/flags/carrier.aconfig
new file mode 100644
index 0000000..14aedc6
--- /dev/null
+++ b/flags/carrier.aconfig
@@ -0,0 +1,32 @@
+package: "com.android.internal.telephony.flags"
+container: "system"
+
+# OWNER=nharold TARGET=24Q4
+flag {
+    name: "async_init_carrier_privileges_tracker"
+    is_exported: true
+    namespace: "telephony"
+    description: "Offload the heavyweight initialization of CarrierPrivilegesTracker to a worker thread"
+    bug:"357096337"
+}
+
+# OWNER=melhuishj TARGET=25Q1
+flag {
+    name: "cleanup_carrier_app_update_enabled_state_logic"
+    is_exported: true
+    namespace: "telephony"
+    description: "Improve readability of update state logic"
+    bug:"232141900"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=melhuishj TARGET=25Q2
+flag {
+    name: "temporary_failures_in_carrier_messaging_service"
+    is_exported: true
+    namespace: "telephony"
+    description: "Enable temporary failures in CarrierMessagingService"
+    bug:"326610112"
+}
diff --git a/flags/data.aconfig b/flags/data.aconfig
index 0fd094d..4a90c10 100644
--- a/flags/data.aconfig
+++ b/flags/data.aconfig
@@ -121,14 +121,11 @@
   }
 }
 
-# OWNER=jackyu TARGET=25Q1
+# OWNER=jackyu TARGET=25Q2
 flag {
-  name: "sim_disabled_graceful_tear_down"
+  name: "oem_paid_private"
   namespace: "telephony"
-  description: "Gracefully tear down the networks when SIM is disabled."
-  bug: "362372940"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
+  description: "Support OEM_PAID and OEM_PRIVATE networks"
+  bug: "366194627"
+  is_exported: true
 }
-
diff --git a/flags/ims.aconfig b/flags/ims.aconfig
index d2401fe..703440f 100644
--- a/flags/ims.aconfig
+++ b/flags/ims.aconfig
@@ -137,3 +137,32 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+# OWNER=breadley TARGET=25Q1
+flag {
+    name: "ims_resolver_user_aware"
+    namespace: "telephony"
+    description: "When enabled, it makes ImsResolver mult-user aware for configurations like HSUM."
+    bug:"371272669"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=meghapatil TARGET=25Q2
+flag {
+    name: "support_sms_over_ims_apis"
+    namespace: "telephony"
+    description: "Used to expose SMS related hidden APIs for SMS over IMS to public API."
+    bug:"359721349"
+    is_exported: true
+}
+
+# OWNER=jhyoon TARGET=25Q2
+flag {
+    name: "support_ims_mmtel_interface"
+    namespace: "telephony"
+    description: "This flag controls the type of API regarding MmTelFeature, either hidden or system type."
+    bug:"359721349"
+    is_exported: true
+}
diff --git a/flags/misc.aconfig b/flags/misc.aconfig
index 860c864..ec7b8fa 100644
--- a/flags/misc.aconfig
+++ b/flags/misc.aconfig
@@ -80,15 +80,6 @@
 
 # OWNER=rambowang TARGET=24Q3
 flag {
-  name: "show_call_id_and_call_waiting_in_additional_settings_menu"
-  is_exported: true
-  namespace: "telephony"
-  description: "Expose carrier config KEY_ADDITIONAL_SETTINGS_CALLER_ID_VISIBILITY_BOOL and KEY_ADDITIONAL_SETTINGS_CALL_WAITING_VISIBILITY_BOOL."
-  bug: "310264981"
-}
-
-# OWNER=rambowang TARGET=24Q3
-flag {
     name: "reset_mobile_network_settings"
     is_exported: true
     namespace: "telephony"
@@ -229,3 +220,29 @@
     }
 }
 
+# OWNER=jackyu TARGET=25Q2
+flag {
+    name: "power_down_race_fix"
+    namespace: "telephony"
+    description: "Fixed race condition while powering down"
+    bug:"378616116"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=jackyu TARGET=25Q2
+flag {
+    name: "deprecate_cdma"
+    namespace: "telephony"
+    description: "Deprecate CDMA and NV APIS"
+    bug: "379356026"
+}
+
+# OWNER=jackyu TARGET=25Q2
+flag {
+    name: "cleanup_cdma"
+    namespace: "telephony"
+    description: "Disable CDMA and NV backing code"
+    bug: "379356026"
+}
diff --git a/flags/network.aconfig b/flags/network.aconfig
index 7c09ba3..be599ea 100644
--- a/flags/network.aconfig
+++ b/flags/network.aconfig
@@ -93,3 +93,20 @@
     }
 }
 
+# OWNER=yomna TARGET=25Q2
+flag {
+  name: "security_algorithms_update_indications"
+  is_exported: true
+  namespace: "telephony"
+  description: "guard system API onSecurityAlgorithmsChanged"
+  bug: "355062720"
+}
+
+# OWNER=yomna TARGET=25Q2
+flag {
+  name: "cellular_identifier_disclosure_indications"
+  is_exported: true
+  namespace: "telephony"
+  description: "guard system API onCellularIdentifierDisclosedChanged"
+  bug: "355062720"
+}
diff --git a/flags/satellite.aconfig b/flags/satellite.aconfig
index 4806789..ee4570e 100644
--- a/flags/satellite.aconfig
+++ b/flags/satellite.aconfig
@@ -41,6 +41,7 @@
     namespace: "telephony"
     description: "This flag enables satellite carrier roaming to nb iot ntn."
     bug:"348253735"
+    is_exported: true
 }
 
 # OWNER=tnd TARGET=24Q4
@@ -61,4 +62,22 @@
   metadata {
     purpose: PURPOSE_BUGFIX
   }
-}
\ No newline at end of file
+}
+
+# OWNER=amallampati TARGET=25Q2
+flag {
+    name: "satellite_system_apis"
+    is_exported: true
+    namespace: "telephony"
+    description: "Convert hidden SatelliteManager APIs to system APIs."
+    bug:"373436320"
+}
+
+# OWNER=rambowang TARGET=25Q2
+flag {
+  name: "satellite_state_change_listener"
+  namespace: "telephony"
+  description: "Introduce SatelliteManager APIs for carrier apps to monitor satellite state change"
+  bug: "357638490"
+  is_exported: true
+}
diff --git a/flags/subscription.aconfig b/flags/subscription.aconfig
index 76485be..0522a3c 100644
--- a/flags/subscription.aconfig
+++ b/flags/subscription.aconfig
@@ -81,3 +81,12 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+# OWNER=jmattis TARGET=25Q2
+flag {
+  name: "subscription_plan_allow_status_and_end_date"
+  namespace: "telephony"
+  description: "Provide APIs to retrieve the status and recurrence rule info on a subscription plan"
+  bug: "357272015"
+  is_exported: true
+}
diff --git a/flags/uicc.aconfig b/flags/uicc.aconfig
index 14341d9..8561dac 100644
--- a/flags/uicc.aconfig
+++ b/flags/uicc.aconfig
@@ -49,6 +49,7 @@
     namespace: "telephony"
     description: "This flag controls the visibility of the setCarrierRestrictionStatus API in carrierRestrictionRules class."
     bug:"342411308"
+    is_exported: true
 }
 
 # OWNER=arunvoddu TARGET=24Q4
@@ -69,3 +70,59 @@
     description: "This flag controls optimization of apdu sender class."
     bug:"335257880"
 }
+
+# OWNER=arunvoddu TARGET=24Q4
+flag {
+    name: "ignore_carrierid_reset_for_sim_removal"
+    namespace: "telephony"
+    description: "This flag controls the carrierId reset while imsi key deletion time upon sim ejection."
+    bug:"366178705"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=jhyoon TARGET=25Q2
+flag {
+    name: "support_isim_record"
+    namespace: "telephony"
+    description: "This flag controls the type of API that retrieves ISIM records, either hidden or system type."
+    bug:"359721349"
+    is_exported: true
+}
+
+# OWNER=jinjeong TARGET=25Q2
+flag {
+    name: "carrier_id_from_carrier_identifier"
+    namespace: "telephony"
+    description: "This flag controls to get a carrier id using a carrier identifier."
+    bug:"378778278"
+    is_exported: true
+}
+
+# OWNER=arunvoddu TARGET=25Q2
+flag {
+    name: "force_imsi_certificate_delete"
+    namespace: "telephony"
+    description: "This flag controls the IMSI certificate delete with out any condition."
+    bug:"235296888"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=jinjeong TARGET=25Q2
+flag {
+    name: "get_group_id_level2"
+    namespace: "telephony"
+    description: "This flag controls to get a group id level2."
+    bug:"381171540"
+}
+
+# OWNER=jinjeong TARGET=25Q2
+flag {
+    name: "action_sim_preference_settings"
+    namespace: "telephony"
+    description: "This flag controls to launch sim preference page in Setting"
+    bug:"381319469"
+}
diff --git a/lint-baseline.xml b/lint-baseline.xml
new file mode 100644
index 0000000..491d013
--- /dev/null
+++ b/lint-baseline.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha08" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha08">
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IEuiccCardController permission check should be converted to @EnforcePermission annotation"
+        errorLine1="        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, &quot;Requires DUMP&quot;);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/opt/telephony/src/java/com/android/internal/telephony/euicc/EuiccCardController.java"
+            line="1525"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IEuiccController permission check can be converted to @EnforcePermission annotation"
+        errorLine1="        mContext.enforceCallingPermission(Manifest.permission.MASTER_CLEAR,"
+        errorLine2="        ^">
+        <location
+            file="frameworks/opt/telephony/src/java/com/android/internal/telephony/euicc/EuiccController.java"
+            line="1679"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="IEuiccController permission check should be converted to @EnforcePermission annotation"
+        errorLine1="        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, &quot;Requires DUMP&quot;);"
+        errorLine2="        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+        <location
+            file="frameworks/opt/telephony/src/java/com/android/internal/telephony/euicc/EuiccController.java"
+            line="1812"
+            column="9"/>
+    </issue>
+
+    <issue
+        id="SimpleManualPermissionEnforcement"
+        message="ISub permission check should be converted to @EnforcePermission annotation"
+        errorLine1="        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,"
+        errorLine2="        ^">
+        <location
+            file="frameworks/opt/telephony/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java"
+            line="4800"
+            column="9"/>
+    </issue>
+
+</issues>
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto
index 9dbdcb0..8d87a27 100644
--- a/proto/src/persist_atoms.proto
+++ b/proto/src/persist_atoms.proto
@@ -723,6 +723,9 @@
     optional int32 count_of_satellite_allowed_state_changed_events = 31;
     optional int32 count_of_successful_location_queries = 32;
     optional int32 count_of_failed_location_queries = 33;
+    optional int32 count_of_p2p_sms_available_notification_shown = 34;
+    optional int32 count_of_p2p_sms_available_notification_removed = 35;
+    optional bool is_ntn_only_carrier = 36;
 }
 
 message SatelliteSession {
@@ -743,6 +746,9 @@
     optional int32 count_of_satellite_notification_displayed = 15;
     optional int32 count_of_auto_exit_due_to_screen_off = 16;
     optional int32 count_of_auto_exit_due_to_tn_network = 17;
+    optional bool is_emergency = 18;
+    optional bool is_ntn_only_carrier = 19;
+    optional int32 max_inactivity_duration_sec = 20;
 }
 
 message SatelliteIncomingDatagram {
@@ -751,6 +757,7 @@
     optional int64 datagram_transfer_time_millis = 3;
     optional bool is_demo_mode = 4;
     optional int32 carrier_id = 5;
+    optional bool is_ntn_only_carrier = 6;
 }
 
 message SatelliteOutgoingDatagram {
@@ -760,6 +767,7 @@
     optional int64 datagram_transfer_time_millis = 4;
     optional bool is_demo_mode = 5;
     optional int32 carrier_id = 6;
+    optional bool is_ntn_only_carrier = 7;
 }
 
 message SatelliteProvision {
@@ -768,6 +776,7 @@
     optional bool is_provision_request = 3;
     optional bool is_canceled = 4;
     optional int32 carrier_id = 5;
+    optional bool is_ntn_only_carrier = 6;
 }
 
 message SatelliteSosMessageRecommender {
@@ -781,6 +790,7 @@
     optional bool is_satellite_allowed_in_current_location = 8;
     optional bool is_wifi_connected = 9;
     optional int32 carrier_id = 10;
+    optional bool is_ntn_only_carrier = 11;
 }
 
 message DataNetworkValidation {
@@ -851,4 +861,5 @@
     optional int32 config_data_source = 9;
     optional int32 carrier_id = 10;
     optional int32 triggering_event = 11;
+    optional bool is_ntn_only_carrier = 12;
 }
diff --git a/src/java/com/android/internal/telephony/CarrierInfoManager.java b/src/java/com/android/internal/telephony/CarrierInfoManager.java
index 8364c0a..206770d 100644
--- a/src/java/com/android/internal/telephony/CarrierInfoManager.java
+++ b/src/java/com/android/internal/telephony/CarrierInfoManager.java
@@ -273,21 +273,25 @@
 
     /**
      * Resets the Carrier Keys in the database. This involves 2 steps:
-     *  1. Delete the keys from the database.
-     *  2. Send an intent to download new Certificates.
-     * @param context Context
-     * @param mPhoneId phoneId
+     * 1. Delete the keys from the database.
+     * 2. Send an intent to download new Certificates.
      *
+     * @param context       Context
+     * @param mPhoneId      phoneId
+     * @param forceResetAll to skip the check of the RESET_CARRIER_KEY_RATE_LIMIT.
      */
-    public void resetCarrierKeysForImsiEncryption(Context context, int mPhoneId) {
-        Log.i(LOG_TAG, "resetting carrier key");
+    public void resetCarrierKeysForImsiEncryption(Context context, int mPhoneId,
+            boolean forceResetAll) {
+        Log.i(LOG_TAG, "resetting carrier key, forceResetAll = " +forceResetAll);
         // Check rate limit.
         long now = System.currentTimeMillis();
-        if (now - mLastAccessResetCarrierKey < RESET_CARRIER_KEY_RATE_LIMIT) {
-            Log.i(LOG_TAG, "resetCarrierKeysForImsiEncryption: Access rate exceeded");
-            return;
+        if (!forceResetAll) {
+            if (now - mLastAccessResetCarrierKey < RESET_CARRIER_KEY_RATE_LIMIT) {
+                Log.i(LOG_TAG, "resetCarrierKeysForImsiEncryption: Access rate exceeded");
+                return;
+            }
+            mLastAccessResetCarrierKey = now;
         }
-        mLastAccessResetCarrierKey = now;
 
         int subId = SubscriptionManager.getSubscriptionId(mPhoneId);
         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
diff --git a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
index 6f84521..1667b7d 100644
--- a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -146,9 +146,9 @@
                 .createForSubscriptionId(mPhone.getSubId());
         if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
             mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
-        } else {
-            mUserManager = mContext.getSystemService(UserManager.class);
         }
+        mUserManager = mContext.getSystemService(UserManager.class);
+
         CarrierConfigManager carrierConfigManager = mContext.getSystemService(
                 CarrierConfigManager.class);
         // Callback which directly handle config change should be executed on handler thread
@@ -170,17 +170,29 @@
                                                 TelephonyManager.class)
                                         .createForSubscriptionId(subId);
                             }
-                            mCarrierId = carrierId;
+                            if (Flags.ignoreCarrieridResetForSimRemoval()) {
+                                if (carrierId > 0) {
+                                    mCarrierId = carrierId;
+                                }
+                            } else {
+                                mCarrierId = carrierId;
+                            }
                             updateSimOperator();
                             // If device is screen locked do not proceed to handle
                             // EVENT_ALARM_OR_CONFIG_CHANGE
-                            if (mKeyguardManager.isDeviceLocked()) {
-                                logd("Device is Locked");
+                            printDeviceLockStatus();
+                            if (Flags.ignoreCarrieridResetForSimRemoval()) {
+                                if (!mUserManager.isUserUnlocked()) {
+                                    mIsRequiredToHandleUnlock = true;
+                                    return;
+                                }
+                            } else if (mKeyguardManager.isDeviceLocked()) {
                                 mIsRequiredToHandleUnlock = true;
-                            } else {
-                                logd("Carrier Config changed: slotIndex=" + slotIndex);
-                                sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+                                return;
                             }
+                            logd("Carrier Config changed: slotIndex=" + slotIndex);
+                            sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+
                         }
                     } else {
                         boolean isUserUnlocked = mUserManager.isUserUnlocked();
@@ -199,6 +211,11 @@
         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
     }
 
+    private void printDeviceLockStatus() {
+        logd(" Device Status: isDeviceLocked = " + mKeyguardManager.isDeviceLocked()
+                + "  iss User unlocked = " + mUserManager.isUserUnlocked());
+    }
+
     // TODO remove this method upon imsiKeyRetryDownloadOnPhoneUnlock enabled.
     private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
         @Override
@@ -307,11 +324,16 @@
                     if (downloadStartedSuccessfully) {
                         unregisterDefaultNetworkCb(slotIndex);
                     } else {
-                        // If download fails due to the device lock, we will reattempt once the
-                        // device is unlocked.
-                        mIsRequiredToHandleUnlock = mKeyguardManager.isDeviceLocked();
+                        // If download fails due to the device user lock, we will reattempt once
+                        // the device is unlocked.
+                        if (Flags.ignoreCarrieridResetForSimRemoval()) {
+                            mIsRequiredToHandleUnlock = !mUserManager.isUserUnlocked();
+                        } else {
+                            mIsRequiredToHandleUnlock = mKeyguardManager.isDeviceLocked();
+                        }
+
                         loge("hasActiveDataConnection = " + hasActiveDataNetwork
-                                + "    isDeviceLocked = " + mIsRequiredToHandleUnlock);
+                                + "    isDeviceUserLocked = " + mIsRequiredToHandleUnlock);
                         if (!hasActiveDataNetwork) {
                             registerDefaultNetworkCb(slotIndex);
                         }
@@ -538,14 +560,19 @@
                                 carrierKeyDownloadIdentifier);
                     }
                     parseJsonAndPersistKey(jsonStr, mccMnc, carrierId);
+                    logd("Completed downloading keys");
                 } catch (Exception e) {
                     loge( "Error in download:" + carrierKeyDownloadIdentifier
                             + ". " + e);
                 } finally {
                     mDownloadManager.remove(carrierKeyDownloadIdentifier);
                 }
+            } else {
+                loge("Download Failed reason = " + cursor.getInt(columnIndex)
+                        + "Failed Status reason" + cursor.getInt(
+                        cursor.getColumnIndex(DownloadManager.COLUMN_REASON)));
+                printDeviceLockStatus();
             }
-            logd("Completed downloading keys");
         }
         cursor.close();
     }
@@ -796,6 +823,7 @@
         } catch (Exception e) {
             loge( "exception trying to download key from url: " + mURL + ",  Exception = "
                     + e.getMessage());
+            printDeviceLockStatus();
             return false;
         }
         return true;
diff --git a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
index 522cf77..6326d6c 100644
--- a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
@@ -46,6 +46,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
@@ -68,7 +69,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.telephony.flags.FeatureFlags;
-import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.UiccPort;
 import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.telephony.Rlog;
@@ -80,7 +80,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -179,6 +178,8 @@
     private static final int ACTION_SET_TEST_OVERRIDE_CARRIER_SERVICE_PACKAGE = 11;
 
     private final Context mContext;
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
     private final Phone mPhone;
     private final PackageManager mPackageManager;
     private final UserManager mUserManager;
@@ -198,7 +199,7 @@
     @Nullable private List<UiccAccessRule> mTestOverrideRules = null;
     @Nullable private String mTestOverrideCarrierServicePackage = null;
     // Map of PackageName -> Certificate hashes for that Package
-    @NonNull private final Map<String, Set<String>> mInstalledPackageCerts = new ArrayMap<>();
+    @NonNull private final Map<String, Set<Integer>> mInstalledPackageCertHashes = new ArrayMap<>();
     // Map of PackageName -> UIDs for that Package
     @NonNull private final Map<String, Set<Integer>> mCachedUids = new ArrayMap<>();
 
@@ -226,8 +227,7 @@
             "mPrivilegedPackageInfoLock.writeLock()"})
     private boolean mSimIsReadyButNotLoaded = false;
 
-    @NonNull
-    private final FeatureFlags mFeatureFlags;
+    private volatile Handler mCurrentHandler;
 
     /** Small snapshot to hold package names and UIDs of privileged packages. */
     private static final class PrivilegedPackageInfo {
@@ -303,7 +303,9 @@
                                 return;
                             }
 
-                            sendMessage(obtainMessage(ACTION_SIM_STATE_UPDATED, slotId, simState));
+                            mCurrentHandler.sendMessage(
+                                    mCurrentHandler.obtainMessage(
+                                            ACTION_SIM_STATE_UPDATED, slotId, simState));
                             break;
                         }
                         case Intent.ACTION_PACKAGE_ADDED: // fall through
@@ -335,19 +337,21 @@
                                     ? ACTION_PACKAGE_REMOVED_OR_DISABLED_BY_USER
                                     : ACTION_PACKAGE_ADDED_REPLACED_OR_CHANGED;
 
-                            sendMessage(obtainMessage(what, pkgName));
+                            mCurrentHandler.sendMessage(
+                                    mCurrentHandler.obtainMessage(what, pkgName));
                             break;
                         }
                     }
                 }
             };
 
-    public CarrierPrivilegesTracker(@NonNull Looper looper, @NonNull Phone phone,
-            @NonNull Context context, @NonNull FeatureFlags flags) {
+    public CarrierPrivilegesTracker(
+            @NonNull Looper looper, @NonNull Phone phone,
+            @NonNull Context context, @NonNull FeatureFlags featureFlags) {
         super(looper);
-        mContext = context;
-        mFeatureFlags = flags;
         mPhone = phone;
+        mContext = context;
+        mFeatureFlags = featureFlags;
         mPackageManager = mContext.getPackageManager();
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mCarrierConfigManager =
@@ -363,6 +367,56 @@
                 (TelephonyRegistryManager)
                         mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
 
+        if (mFeatureFlags.asyncInitCarrierPrivilegesTracker()) {
+            final Object localLock = new Object();
+            HandlerThread initializerThread =
+                    new HandlerThread("CarrierPrivilegesTracker Initializer") {
+                        @Override
+                        protected void onLooperPrepared() {
+                            synchronized (localLock) {
+                                localLock.notifyAll();
+                            }
+                        }
+                    };
+            synchronized (localLock) {
+                initializerThread.start();
+                while (true) {
+                    try {
+                        localLock.wait();
+                        break;
+                    } catch (InterruptedException ie) {
+                    }
+                }
+            }
+            mCurrentHandler = new Handler(initializerThread.getLooper()) {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch(msg.what) {
+                        case ACTION_INITIALIZE_TRACKER:
+                            handleInitializeTracker();
+                            if (!hasMessagesOrCallbacks()) {
+                                mCurrentHandler = CarrierPrivilegesTracker.this;
+                                initializerThread.quitSafely();
+                            }
+                            break;
+                        default:
+                            Message m = CarrierPrivilegesTracker.this.obtainMessage();
+                            m.copyFrom(msg);
+                            m.sendToTarget();
+                            if (!hasMessagesOrCallbacks()) {
+                                mCurrentHandler = CarrierPrivilegesTracker.this;
+                                initializerThread.quitSafely();
+                            }
+                            break;
+                    }
+                }
+            };
+        } else {
+            mCurrentHandler = this;
+        }
+
+        mCurrentHandler.sendMessage(obtainMessage(ACTION_INITIALIZE_TRACKER));
+
         IntentFilter certFilter = new IntentFilter();
         certFilter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
         certFilter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
@@ -391,7 +445,6 @@
             mContext.registerReceiver(mIntentReceiver, packageFilter);
         }
 
-        sendMessage(obtainMessage(ACTION_INITIALIZE_TRACKER));
     }
 
     @Override
@@ -499,7 +552,7 @@
                     && mClearUiccRulesUptimeMillis == CLEAR_UICC_RULE_NOT_SCHEDULED) {
                 mClearUiccRulesUptimeMillis =
                         SystemClock.uptimeMillis() + CLEAR_UICC_RULES_DELAY_MILLIS;
-                sendMessageAtTime(obtainMessage(ACTION_CLEAR_UICC_RULES),
+                mCurrentHandler.sendMessageAtTime(obtainMessage(ACTION_CLEAR_UICC_RULES),
                         mClearUiccRulesUptimeMillis);
                 mLocalLog.log("SIM is gone, simState=" + TelephonyManager.simStateToString(simState)
                         + ". Delay " + TimeUnit.MILLISECONDS.toSeconds(
@@ -556,25 +609,29 @@
         return uiccProfile.getCarrierPrivilegeAccessRules();
     }
 
-    private void handlePackageAddedReplacedOrChanged(@Nullable String pkgName) {
-        if (pkgName == null) return;
+    private PackageInfo getPackageInfoForPackage(@Nullable String pkgName) {
+        if (pkgName == null) return null;
 
         PackageInfo pkg;
         try {
-            pkg =
-                    mFeatureFlags.supportCarrierServicesForHsum()
-                            ? mPackageManager.getPackageInfoAsUser(
-                                    pkgName,
-                                    INSTALLED_PACKAGES_QUERY_FLAGS,
-                                    ActivityManager.getCurrentUser())
-                            : mPackageManager.getPackageInfo(
-                                    pkgName, INSTALLED_PACKAGES_QUERY_FLAGS);
+            return mFeatureFlags.supportCarrierServicesForHsum()
+                        ? mPackageManager.getPackageInfoAsUser(
+                                pkgName,
+                                INSTALLED_PACKAGES_QUERY_FLAGS,
+                                ActivityManager.getCurrentUser())
+                        : mPackageManager.getPackageInfo(
+                                pkgName, INSTALLED_PACKAGES_QUERY_FLAGS);
         } catch (NameNotFoundException e) {
             Rlog.e(TAG, "Error getting installed package: " + pkgName, e);
-            return;
+            return null;
         }
+    }
 
-        updateCertsForPackage(pkg);
+    private void handlePackageAddedReplacedOrChanged(@Nullable String pkgName) {
+        PackageInfo pkg = getPackageInfoForPackage(pkgName);
+        if (pkg == null) return;
+
+        updateCertHashHashesForPackage(pkg);
         // Invalidate cache because this may be a package already on the device but getting
         // installed for a user it wasn't installed in before, which means there will be an
         // additional UID.
@@ -582,30 +639,46 @@
         if (VDBG) {
             Rlog.d(TAG, "Package added/replaced/changed:"
                     + " pkg=" + Rlog.pii(TAG, pkgName)
-                    + " cert hashes=" + mInstalledPackageCerts.get(pkgName));
+                    + " cert hashes=" + mInstalledPackageCertHashes.get(pkgName));
         }
 
         maybeUpdatePrivilegedPackagesAndNotifyRegistrants();
     }
 
-    private void updateCertsForPackage(@NonNull PackageInfo pkg) {
-        Set<String> certs = new ArraySet<>(1);
+    private void updateCertHashHashesForPackage(@NonNull PackageInfo pkg) {
+        Set<Integer> certs = new ArraySet<>(2);
         List<Signature> signatures = UiccAccessRule.getSignatures(pkg);
         for (Signature signature : signatures) {
             byte[] sha1 = UiccAccessRule.getCertHash(signature, SHA_1);
-            certs.add(IccUtils.bytesToHexString(sha1).toUpperCase(Locale.ROOT));
+            certs.add(UiccAccessRule.getCertificateHashHashCode(sha1));
 
             byte[] sha256 = UiccAccessRule.getCertHash(signature, SHA_256);
-            certs.add(IccUtils.bytesToHexString(sha256).toUpperCase(Locale.ROOT));
+            certs.add(UiccAccessRule.getCertificateHashHashCode(sha256));
         }
 
-        mInstalledPackageCerts.put(pkg.packageName, certs);
+        mInstalledPackageCertHashes.put(pkg.packageName, certs);
+    }
+
+    private Set<byte[]> getCertsForPackage(@NonNull String pkgName) {
+        PackageInfo pkg = getPackageInfoForPackage(pkgName);
+        if (pkg == null) return Collections.emptySet();
+
+        List<Signature> signatures = UiccAccessRule.getSignatures(pkg);
+
+        ArraySet<byte[]> certs = new ArraySet<>(2);
+        for (Signature signature : signatures) {
+            certs.add(UiccAccessRule.getCertHash(signature, SHA_1));
+            certs.add(UiccAccessRule.getCertHash(signature, SHA_256));
+        }
+
+        return certs;
     }
 
     private void handlePackageRemovedOrDisabledByUser(@Nullable String pkgName) {
         if (pkgName == null) return;
 
-        if (mInstalledPackageCerts.remove(pkgName) == null || mCachedUids.remove(pkgName) == null) {
+        if (mInstalledPackageCertHashes.remove(pkgName) == null
+                || mCachedUids.remove(pkgName) == null) {
             Rlog.e(TAG, "Unknown package was uninstalled or disabled by user: " + pkgName);
             return;
         }
@@ -637,7 +710,7 @@
             msg +=
                     " installed pkgs="
                             + getObfuscatedPackages(
-                                    mInstalledPackageCerts.entrySet(),
+                                    mInstalledPackageCertHashes.entrySet(),
                                     e -> "pkg(" + Rlog.pii(TAG, e.getKey()) + ")=" + e.getValue());
         }
         mLocalLog.log(msg);
@@ -651,7 +724,7 @@
                                 ? ActivityManager.getCurrentUser()
                                 : UserHandle.SYSTEM.getIdentifier());
         for (PackageInfo pkg : installedPackages) {
-            updateCertsForPackage(pkg);
+            updateCertHashHashesForPackage(pkg);
             // This may be unnecessary before initialization, but invalidate the cache all the time
             // just in case to ensure consistency.
             getUidsForPackage(pkg.packageName, /* invalidateCache= */ true);
@@ -740,8 +813,12 @@
         Set<String> carrierServiceEligiblePackages = new ArraySet<>();
         Set<String> privilegedPackageNames = new ArraySet<>();
         Set<Integer> privilegedUids = new ArraySet<>();
-        for (Map.Entry<String, Set<String>> e : mInstalledPackageCerts.entrySet()) {
-            final int priv = getPackagePrivilegedStatus(e.getKey(), e.getValue());
+        for (Map.Entry<String, Set<Integer>> e : mInstalledPackageCertHashes.entrySet()) {
+            if (!isPackageMaybePrivileged(e.getKey(), e.getValue())) continue;
+
+            Set<byte[]> fullCerts = getCertsForPackage(e.getKey());
+
+            final int priv = getPackagePrivilegedStatus(e.getKey(), fullCerts);
             switch (priv) {
                 case PACKAGE_PRIVILEGED_FROM_SIM:
                 case PACKAGE_PRIVILEGED_FROM_CARRIER_SERVICE_TEST_OVERRIDE: // fallthrough
@@ -760,32 +837,58 @@
                 getCarrierService(carrierServiceEligiblePackages));
     }
 
+    private boolean isPackageMaybePrivileged(
+            @NonNull String pkgName, @NonNull Set<Integer> hashHashes) {
+        for (Integer hashHash : hashHashes) {
+            // Non-null (whether empty or not) test override rule will ignore the UICC and CC rules
+            if (mTestOverrideRules != null) {
+                for (UiccAccessRule rule : mTestOverrideRules) {
+                    if (rule.hasMatchingCertificateHashHashAndPackageName(hashHash, pkgName)) {
+                        return true;
+                    }
+                }
+            } else {
+                for (UiccAccessRule rule : mUiccRules) {
+                    if (rule.hasMatchingCertificateHashHashAndPackageName(hashHash, pkgName)) {
+                        return true;
+                    }
+                }
+                for (UiccAccessRule rule : mCarrierConfigRules) {
+                    if (rule.hasMatchingCertificateHashHashAndPackageName(hashHash, pkgName)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Returns the privilege status of the provided package.
      *
      * <p>Returned privilege status depends on whether a package matches the certificates from
      * carrier config, from test overrides or from certificates stored on the SIM.
      */
-    private int getPackagePrivilegedStatus(@NonNull String pkgName, @NonNull Set<String> certs) {
+    private int getPackagePrivilegedStatus(@NonNull String pkgName, @NonNull Set<byte[]> certs) {
         // Double-nested for loops, but each collection should contain at most 2 elements in nearly
         // every case.
         // TODO(b/184382310) find a way to speed this up
-        for (String cert : certs) {
+        for (byte[] cert : certs) {
             // Non-null (whether empty or not) test override rule will ignore the UICC and CC rules
             if (mTestOverrideRules != null) {
                 for (UiccAccessRule rule : mTestOverrideRules) {
-                    if (rule.matches(cert, pkgName)) {
+                    if (rule.hasMatchingCertificateHashAndPackageName(cert, pkgName)) {
                         return PACKAGE_PRIVILEGED_FROM_SIM;
                     }
                 }
             } else {
                 for (UiccAccessRule rule : mUiccRules) {
-                    if (rule.matches(cert, pkgName)) {
+                    if (rule.hasMatchingCertificateHashAndPackageName(cert, pkgName)) {
                         return PACKAGE_PRIVILEGED_FROM_SIM;
                     }
                 }
                 for (UiccAccessRule rule : mCarrierConfigRules) {
-                    if (rule.matches(cert, pkgName)) {
+                    if (rule.hasMatchingCertificateHashAndPackageName(cert, pkgName)) {
                         return pkgName.equals(mTestOverrideCarrierServicePackage)
                                 ? PACKAGE_PRIVILEGED_FROM_CARRIER_SERVICE_TEST_OVERRIDE
                                 : PACKAGE_PRIVILEGED_FROM_CARRIER_CONFIG;
@@ -856,7 +959,7 @@
             pw.println(
                     "CarrierPrivilegesTracker - Obfuscated Pkgs + Certs: "
                             + getObfuscatedPackages(
-                                    mInstalledPackageCerts.entrySet(),
+                                    mInstalledPackageCertHashes.entrySet(),
                                     e -> "pkg(" + Rlog.pii(TAG, e.getKey()) + ")=" + e.getValue()));
         }
         pw.println("mClearUiccRulesUptimeMillis: " + mClearUiccRulesUptimeMillis);
@@ -872,7 +975,8 @@
      * @see TelephonyManager#setCarrierTestOverride
      */
     public void setTestOverrideCarrierPrivilegeRules(@Nullable String carrierPrivilegeRules) {
-        sendMessage(obtainMessage(ACTION_SET_TEST_OVERRIDE_RULE, carrierPrivilegeRules));
+        mCurrentHandler.sendMessage(
+                obtainMessage(ACTION_SET_TEST_OVERRIDE_RULE, carrierPrivilegeRules));
     }
 
     /**
@@ -888,7 +992,7 @@
      * @see TelephonyManager#setCarrierServicePackageOverride
      */
     public void setTestOverrideCarrierServicePackage(@Nullable String carrierServicePackage) {
-        sendMessage(obtainMessage(
+        mCurrentHandler.sendMessage(obtainMessage(
                 ACTION_SET_TEST_OVERRIDE_CARRIER_SERVICE_PACKAGE, carrierServicePackage));
     }
 
diff --git a/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
index 55baecc..4f9d84d 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -161,7 +162,11 @@
     };
 
     public CarrierServiceBindHelper(Context context) {
-        mContext = context.createContextAsUser(Process.myUserHandle(), 0);
+        mContext =
+                context.createContextAsUser(
+                        Flags.supportCarrierServicesForHsum()
+                        ? UserHandle.of(ActivityManager.getCurrentUser())
+                        : Process.myUserHandle(), 0);
 
         updateBindingsAndSimStates();
 
diff --git a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
index d47c2c4..b470e2e 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
@@ -75,7 +75,8 @@
 
 
     @VisibleForTesting
-    public static final String ACTION_NEVER_ASK_AGAIN = "SilenceNoWifiEmrgCallingNotification";
+    public static final String ACTION_NEVER_ASK_AGAIN =
+            "com.android.internal.telephony.action.SILENCE_WIFI_CALLING_NOTIFICATION";
     public final NotificationActionReceiver mActionReceiver = new NotificationActionReceiver();
 
     @VisibleForTesting
@@ -733,6 +734,7 @@
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(ACTION_NEVER_ASK_AGAIN)) {
                 Rlog.i(LOG_TAG, "NotificationActionReceiver: ACTION_NEVER_ASK_AGAIN");
+                dismissEmergencyCallingNotification();
                 // insert a key to silence future notifications
                 SharedPreferences.Editor editor =
                         PreferenceManager.getDefaultSharedPreferences(context).edit();
@@ -743,5 +745,22 @@
                 context.unregisterReceiver(mActionReceiver);
             }
         }
+
+        /**
+         * Dismiss the notification when the "Do Not Ask Again" button is clicked
+         */
+        private void dismissEmergencyCallingNotification() {
+            if (!mFeatureFlags.stopSpammingEmergencyNotification()) {
+                return;
+            }
+            try {
+                NotificationType t = mNotificationTypeMap.get(NOTIFICATION_EMERGENCY_NETWORK);
+                if (t != null) {
+                    cancelNotification(t);
+                }
+            } catch (Exception e) {
+                Rlog.e(LOG_TAG, "dismissEmergencyCallingNotification", e);
+            }
+        }
     }
 }
diff --git a/src/java/com/android/internal/telephony/CommandsInterface.java b/src/java/com/android/internal/telephony/CommandsInterface.java
index ee7447c..1b19c99 100644
--- a/src/java/com/android/internal/telephony/CommandsInterface.java
+++ b/src/java/com/android/internal/telephony/CommandsInterface.java
@@ -2952,4 +2952,39 @@
      * @param h Handler to be removed from the registrant list.
      */
     default void unregisterForSecurityAlgorithmUpdates(Handler h) {}
+
+    /**
+     * Set the non-terrestrial PLMN with lower priority than terrestrial networks.
+     *
+     * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
+     *                this information to determine the relevant carrier.
+     * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks
+     *                        supported by user subscription.
+     * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite
+     *                             PLMNs that are not supported by the carrier and make sure not to
+     *                             attach to them.
+     * @param result Callback message to receive the result.
+     */
+    default void setSatellitePlmn(int simSlot,
+            @NonNull List<String> carrierPlmnList, @NonNull List<String> allSatellitePlmnList,
+            Message result) {}
+
+    /**
+     * Enable or disable satellite in the cellular modem associated with a carrier.
+     *
+     * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
+     *                this information to determine the relevant carrier.
+     * @param satelliteEnabled {@code true} to enable satellite, {@code false} to disable satellite.
+     * @param result Callback message to receive the result.
+     */
+    default void setSatelliteEnabledForCarrier(int simSlot, boolean satelliteEnabled,
+            Message result) {}
+
+    /**
+     * Check whether satellite is enabled in the cellular modem associated with a carrier.
+     *
+     * @param simSlot Indicates the SIM slot to which this API will be applied.
+     * @param result Callback message to receive the result.
+     */
+    default void isSatelliteEnabledForCarrier(int simSlot, Message result) {}
 }
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index da17f60..dc05608 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -25,11 +25,14 @@
 import android.telephony.CallQuality;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
+import android.telephony.CellularIdentifierDisclosure;
 import android.telephony.LinkCapacityEstimate;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseCallState;
 import android.telephony.PreciseDataConnectionState;
+import android.telephony.SecurityAlgorithmUpdate;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager.DataEnabledReason;
@@ -40,6 +43,7 @@
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.NtnSignalStrength;
 
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.telephony.Rlog;
@@ -303,14 +307,28 @@
     }
 
     @Override
-    public void notifyCallbackModeStarted(Phone sender, @EmergencyCallbackModeType int type) {
-        mTelephonyRegistryMgr.notifyCallBackModeStarted(sender.getPhoneId(),
-                sender.getSubId(), type);
+    public void notifyCallbackModeStarted(Phone sender, @EmergencyCallbackModeType int type,
+            long durationMillis) {
+        if (!mFeatureFlags.emergencyCallbackModeNotification()) return;
+
+        mTelephonyRegistryMgr.notifyCallbackModeStarted(sender.getPhoneId(),
+                sender.getSubId(), type, durationMillis);
+    }
+
+    @Override
+    public void notifyCallbackModeRestarted(Phone sender, @EmergencyCallbackModeType int type,
+            long durationMillis) {
+        if (!mFeatureFlags.emergencyCallbackModeNotification()) return;
+
+        mTelephonyRegistryMgr.notifyCallbackModeRestarted(sender.getPhoneId(),
+                sender.getSubId(), type, durationMillis);
     }
 
     @Override
     public void notifyCallbackModeStopped(Phone sender, @EmergencyCallbackModeType int type,
             @EmergencyCallbackModeStopReason int reason) {
+        if (!mFeatureFlags.emergencyCallbackModeNotification()) return;
+
         mTelephonyRegistryMgr.notifyCallbackModeStopped(sender.getPhoneId(),
                 sender.getSubId(), type, reason);
     }
@@ -326,6 +344,37 @@
                 sender.getSubId(), eligible);
     }
 
+    @Override
+    public void notifyCarrierRoamingNtnAvailableServicesChanged(
+            Phone sender, @NetworkRegistrationInfo.ServiceType int[] availableServices) {
+        mTelephonyRegistryMgr.notifyCarrierRoamingNtnAvailableServicesChanged(
+                sender.getSubId(), availableServices);
+    }
+
+    @Override
+    public void notifyCarrierRoamingNtnSignalStrengthChanged(Phone sender,
+            @NonNull NtnSignalStrength ntnSignalStrength) {
+        mTelephonyRegistryMgr.notifyCarrierRoamingNtnSignalStrengthChanged(
+                sender.getSubId(), ntnSignalStrength);
+    }
+
+    @Override
+    public void notifySecurityAlgorithmsChanged(Phone sender, SecurityAlgorithmUpdate update) {
+        if (!mFeatureFlags.securityAlgorithmsUpdateIndications()) return;
+
+        mTelephonyRegistryMgr.notifySecurityAlgorithmsChanged(sender.getPhoneId(),
+                sender.getSubId(), update);
+    }
+
+    @Override
+    public void notifyCellularIdentifierDisclosedChanged(Phone sender,
+            CellularIdentifierDisclosure disclosure) {
+        if (!mFeatureFlags.cellularIdentifierDisclosureIndications()) return;
+
+        mTelephonyRegistryMgr.notifyCellularIdentifierDisclosedChanged(sender.getPhoneId(),
+                sender.getSubId(), disclosure);
+    }
+
     /**
      * Convert the {@link Call.State} enum into the PreciseCallState.PRECISE_CALL_STATE_* constants
      * for the public API.
diff --git a/src/java/com/android/internal/telephony/DisplayInfoController.java b/src/java/com/android/internal/telephony/DisplayInfoController.java
index d3f0264..57ea022 100644
--- a/src/java/com/android/internal/telephony/DisplayInfoController.java
+++ b/src/java/com/android/internal/telephony/DisplayInfoController.java
@@ -110,11 +110,18 @@
         mTelephonyDisplayInfo = new TelephonyDisplayInfo(
                 TelephonyManager.NETWORK_TYPE_UNKNOWN,
                 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
-                false);
+                false, false, false);
         mNetworkTypeController = new NetworkTypeController(phone, this, featureFlags);
         // EVENT_UPDATE will transition from DefaultState to the current state
         // and update the TelephonyDisplayInfo based on the current state.
         mNetworkTypeController.sendMessage(NetworkTypeController.EVENT_UPDATE);
+
+        // To Support Satellite bandwidth constrained data capability status at telephony
+        // display info
+        if (mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            log("register for satellite network callback");
+            mNetworkTypeController.registerForSatelliteNetwork();
+        }
     }
 
     /**
@@ -129,17 +136,23 @@
      * NetworkTypeController.
      */
     public void updateTelephonyDisplayInfo() {
-        TelephonyDisplayInfo newDisplayInfo = new TelephonyDisplayInfo(
-                mNetworkTypeController.getDataNetworkType(),
-                mNetworkTypeController.getOverrideNetworkType(),
-                isRoaming());
-        if (!newDisplayInfo.equals(mTelephonyDisplayInfo)) {
-            logl("TelephonyDisplayInfo changed from " + mTelephonyDisplayInfo + " to "
-                    + newDisplayInfo);
-            validateDisplayInfo(newDisplayInfo);
-            mTelephonyDisplayInfo = newDisplayInfo;
-            mTelephonyDisplayInfoChangedRegistrants.notifyRegistrants();
-            mPhone.notifyDisplayInfoChanged(mTelephonyDisplayInfo);
+        if (mNetworkTypeController != null && mServiceState != null) {
+            TelephonyDisplayInfo newDisplayInfo = new TelephonyDisplayInfo(
+                    mNetworkTypeController.getDataNetworkType(),
+                    mNetworkTypeController.getOverrideNetworkType(),
+                    isRoaming(),
+                    mServiceState.isUsingNonTerrestrialNetwork(),
+                    mNetworkTypeController.getSatelliteConstrainedData());
+            if (!newDisplayInfo.equals(mTelephonyDisplayInfo)) {
+                logl("TelephonyDisplayInfo changed from " + mTelephonyDisplayInfo + " to "
+                        + newDisplayInfo);
+                validateDisplayInfo(newDisplayInfo);
+                mTelephonyDisplayInfo = newDisplayInfo;
+                mTelephonyDisplayInfoChangedRegistrants.notifyRegistrants();
+                mPhone.notifyDisplayInfoChanged(mTelephonyDisplayInfo);
+            }
+        } else {
+            loge("Found null object");
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/EventLogTags.logtags b/src/java/com/android/internal/telephony/EventLogTags.logtags
index b5e458b..2f30c33 100644
--- a/src/java/com/android/internal/telephony/EventLogTags.logtags
+++ b/src/java/com/android/internal/telephony/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
 
 option java_package com.android.internal.telephony;
 
diff --git a/src/java/com/android/internal/telephony/FdnUtils.java b/src/java/com/android/internal/telephony/FdnUtils.java
index 23cab44..453b3fc 100644
--- a/src/java/com/android/internal/telephony/FdnUtils.java
+++ b/src/java/com/android/internal/telephony/FdnUtils.java
@@ -121,7 +121,8 @@
         try {
             PhoneNumber phoneNumber = phoneNumberUtil.parse(dialStr, defaultCountryIso);
             dialStrE164 = phoneNumberUtil.format(phoneNumber, PhoneNumberFormat.E164);
-            dialStrNational = String.valueOf(phoneNumber.getNationalNumber());
+            dialStrNational = String.valueOf(
+                    phoneNumberUtil.getNationalSignificantNumber(phoneNumber));
         } catch (NumberParseException ignored) {
             Rlog.w(LOG_TAG, "isFDN: could not parse dialStr");
             dialStr = extractSMSC(dialStr);
diff --git a/src/java/com/android/internal/telephony/GbaManager.java b/src/java/com/android/internal/telephony/GbaManager.java
index 7c5f636..047d5d5 100644
--- a/src/java/com/android/internal/telephony/GbaManager.java
+++ b/src/java/com/android/internal/telephony/GbaManager.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -27,6 +28,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.telephony.IBootstrapAuthenticationCallback;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -75,7 +77,8 @@
     private Handler mHandler;
 
     private String mServicePackageName;
-    private String mServicePackageNameOverride;
+    @UserIdInt
+    private int mUserId = UserHandle.USER_SYSTEM;
     private int mReleaseTime;
     private int mRetryTimes = 0;
 
@@ -426,8 +429,9 @@
         try {
             logv("Trying to bind " + servicePackage);
             mServiceConnection = new GbaServiceConnection();
-            if (!mContext.bindService(intent, mServiceConnection,
-                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE)) {
+            if (!mContext.bindServiceAsUser(intent, mServiceConnection,
+                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                    UserHandle.of(mUserId))) {
                 logd("Cannot bind to the service.");
                 retryBind();
                 return;
@@ -462,12 +466,13 @@
     }
 
     /** override GBA service package name to be connected */
-    public boolean overrideServicePackage(String packageName) {
+    public boolean overrideServicePackage(String packageName, @UserIdInt int userId) {
         synchronized (this) {
-            if (!TextUtils.equals(mServicePackageName, packageName)) {
+            if (!TextUtils.equals(mServicePackageName, packageName) || userId != mUserId) {
                 logv("Service package name is changed from " + mServicePackageName
-                        + " to " + packageName);
+                        + " to " + packageName + ", user id from " + mUserId + " to " + userId);
                 mServicePackageName = packageName;
+                mUserId = userId;
                 if (!mHandler.hasMessages(EVENT_CONFIG_CHANGED)) {
                     mHandler.sendEmptyMessage(EVENT_CONFIG_CHANGED);
                 }
diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
index 26d4e1b..14bd273 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
@@ -509,7 +509,8 @@
                         obtainCompleteMessage());
                     }
                 };
-                EmergencyStateTracker.getInstance().exitEmergencyCallbackMode(onComplete);
+                EmergencyStateTracker.getInstance().exitEmergencyCallbackMode(onComplete,
+                        TelephonyManager.STOP_REASON_OUTGOING_NORMAL_CALL_INITIATED);
             } else {
                 mPhone.exitEmergencyCallbackMode();
                 mPhone.setOnEcbModeExitResponse(this, EVENT_EXIT_ECM_RESPONSE_CDMA, null);
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 5d59327..f190a43 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -42,7 +42,6 @@
 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;
@@ -391,8 +390,8 @@
 
         mCarrierResolver = mTelephonyComponentFactory.inject(CarrierResolver.class.getName())
                 .makeCarrierResolver(this, featureFlags);
-        mCarrierPrivilegesTracker = new CarrierPrivilegesTracker(Looper.myLooper(), this, context,
-                featureFlags);
+        mCarrierPrivilegesTracker = new CarrierPrivilegesTracker(
+                Looper.myLooper(), this, context, featureFlags);
 
         getCarrierActionAgent().registerForCarrierAction(
                 CarrierActionAgent.CARRIER_ACTION_SET_METERED_APNS_ENABLED, this,
@@ -470,12 +469,6 @@
         }
     };
 
-    private boolean hasCalling() {
-        if (!TelephonyCapabilities.minimalTelephonyCdmCheck(mFeatureFlags)) return true;
-        return mContext.getPackageManager().hasSystemFeature(
-            PackageManager.FEATURE_TELEPHONY_CALLING);
-    }
-
     private void initOnce(CommandsInterface ci) {
         if (ci instanceof SimulatedRadioControl) {
             mSimulatedRadioControl = (SimulatedRadioControl) ci;
@@ -782,6 +775,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @Override
     public int getPhoneType() {
+        if (mFeatureFlags.cleanupCdma()) return PhoneConstants.PHONE_TYPE_GSM;
         if (mPrecisePhoneType == PhoneConstants.PHONE_TYPE_GSM) {
             return PhoneConstants.PHONE_TYPE_GSM;
         } else {
@@ -1900,8 +1894,15 @@
     @Override
     public void setRadioPowerForReason(boolean power, boolean forEmergencyCall,
             boolean isSelectedPhoneForEmergencyCall, boolean forceApply, int reason) {
-        mSST.setRadioPowerForReason(power, forEmergencyCall, isSelectedPhoneForEmergencyCall,
-                forceApply, reason);
+        if (mFeatureFlags.powerDownRaceFix()) {
+            // setRadioPowerForReason can be called by the binder thread. We need to move that into
+            // the main thread to prevent race condition.
+            post(() -> mSST.setRadioPowerForReason(power, forEmergencyCall,
+                    isSelectedPhoneForEmergencyCall, forceApply, reason));
+        } else {
+            mSST.setRadioPowerForReason(power, forEmergencyCall, isSelectedPhoneForEmergencyCall,
+                    forceApply, reason);
+        }
     }
 
     @Override
@@ -2189,7 +2190,12 @@
 
     @Override
     public void resetCarrierKeysForImsiEncryption() {
-        mCIM.resetCarrierKeysForImsiEncryption(mContext, mPhoneId);
+        mCIM.resetCarrierKeysForImsiEncryption(mContext, mPhoneId, false);
+    }
+
+    @Override
+    public void resetCarrierKeysForImsiEncryption(boolean forceResetAll) {
+        mCIM.resetCarrierKeysForImsiEncryption(mContext, mPhoneId, forceResetAll);
     }
 
     @Override
@@ -3770,6 +3776,13 @@
                         && disclosure != null) {
                     mIdentifierDisclosureNotifier.addDisclosure(mContext, getSubId(), disclosure);
                 }
+                if (mFeatureFlags.cellularIdentifierDisclosureIndications()
+                        && mIdentifierDisclosureNotifier != null
+                        && disclosure != null) {
+                    logd("EVENT_CELL_IDENTIFIER_DISCLOSURE for non-Safety Center listeners "
+                            + "phoneId = " + getPhoneId());
+                    mNotifier.notifyCellularIdentifierDisclosedChanged(this, disclosure);
+                }
                 break;
 
             case EVENT_SET_IDENTIFIER_DISCLOSURE_ENABLED_DONE:
@@ -3780,13 +3793,21 @@
 
             case EVENT_SECURITY_ALGORITHM_UPDATE:
                 logd("EVENT_SECURITY_ALGORITHM_UPDATE phoneId = " + getPhoneId());
+
+                ar = (AsyncResult) msg.obj;
+                SecurityAlgorithmUpdate update = (SecurityAlgorithmUpdate) ar.result;
+
                 if (mFeatureFlags.enableModemCipherTransparencyUnsolEvents()
                         && mNullCipherNotifier != null) {
-                    ar = (AsyncResult) msg.obj;
-                    SecurityAlgorithmUpdate update = (SecurityAlgorithmUpdate) ar.result;
                     mNullCipherNotifier.onSecurityAlgorithmUpdate(mContext, getPhoneId(),
                             getSubId(), update);
                 }
+                if (mFeatureFlags.securityAlgorithmsUpdateIndications()
+                        && mNullCipherNotifier != null) {
+                    logd("EVENT_SECURITY_ALGORITHM_UPDATE for non-Safety Center listeners "
+                              + "phoneId = " + getPhoneId());
+                    mNotifier.notifySecurityAlgorithmsChanged(this, update);
+                }
                 break;
 
             case EVENT_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED_DONE:
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index 3141406..9eebc60 100644
--- a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -48,6 +48,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
+import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.uicc.IccConstants;
@@ -1511,8 +1512,7 @@
     @VisibleForTesting
     public void notifyIfOutgoingEmergencySms(String destAddr) {
         Phone[] allPhones = mPhoneFactoryProxy.getPhones();
-        EmergencyNumber emergencyNumber = mPhone.getEmergencyNumberTracker().getEmergencyNumber(
-                destAddr);
+        EmergencyNumber emergencyNumber = getEmergencyNumber(mPhone, destAddr);
         if (emergencyNumber != null) {
             mPhone.notifyOutgoingEmergencySms(emergencyNumber);
         } else if (allPhones.length > 1) {
@@ -1522,8 +1522,7 @@
                 if (phone.getPhoneId() == mPhone.getPhoneId()) {
                     continue;
                 }
-                emergencyNumber = phone.getEmergencyNumberTracker()
-                        .getEmergencyNumber(destAddr);
+                emergencyNumber = getEmergencyNumber(phone, destAddr);
                 if (emergencyNumber != null) {
                     mPhone.notifyOutgoingEmergencySms(emergencyNumber);
                     break;
@@ -1532,6 +1531,13 @@
         }
     }
 
+    private EmergencyNumber getEmergencyNumber(Phone phone, String number) {
+        if (!phone.hasCalling()) return null;
+        EmergencyNumberTracker tracker = phone.getEmergencyNumberTracker();
+        if (tracker == null) return null;
+        return tracker.getEmergencyNumber(number);
+    }
+
     private void returnUnspecifiedFailure(PendingIntent pi) {
         if (pi != null) {
             try {
diff --git a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
index c94480e..6955a55 100644
--- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
+++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
@@ -202,8 +202,7 @@
                         tracker.onSent(mContext);
                         mTrackers.remove(token);
                         mPhone.notifySmsSent(tracker.mDestAddress);
-                        mSmsDispatchersController.notifySmsSent(
-                                tracker.mDestAddress, tracker.mMessageId, true,
+                        mSmsDispatchersController.notifySmsSent(tracker, true,
                                 tracker.isSinglePartOrLastPart(), true /*success*/);
                         break;
                     case ImsSmsImplBase.SEND_STATUS_ERROR:
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index ca03f5d..52ceda5 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -66,6 +66,7 @@
 import android.telephony.SmsMessage;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Pair;
 
@@ -77,6 +78,7 @@
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.internal.telephony.satellite.metrics.CarrierRoamingSatelliteSessionStats;
 import com.android.internal.telephony.util.NotificationChannelController;
 import com.android.internal.telephony.util.TelephonyUtils;
@@ -687,6 +689,16 @@
             result = RESULT_SMS_DISPATCH_FAILURE;
         }
 
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            if (result == Intents.RESULT_SMS_HANDLED) {
+                SatelliteController satelliteController = SatelliteController.getInstance();
+                if (satelliteController != null
+                        && satelliteController.shouldSendSmsToDatagramDispatcher(mPhone)) {
+                    satelliteController.onSmsReceived(mPhone.getSubId());
+                }
+            }
+        }
+
         // RESULT_OK means that the SMS will be acknowledged by special handling,
         // e.g. for SMS-PP data download. Any other result, we should ack here.
         if (result != Activity.RESULT_OK) {
@@ -747,6 +759,20 @@
             return Intents.RESULT_SMS_HANDLED;
         }
 
+        if (isMtSmsPollingMessage(smsb)) {
+            log("Received MT SMS polling message. Ignored.");
+            return Intents.RESULT_SMS_HANDLED;
+        }
+
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            SatelliteController satelliteController = SatelliteController.getInstance();
+            if (satelliteController != null
+                    && satelliteController.shouldDropSms(mPhone)) {
+                log("SMS not supported during satellite session.");
+                return Intents.RESULT_SMS_HANDLED;
+            }
+        }
+
         int result = dispatchMessageRadioSpecific(smsb, smsSource, token);
 
         // In case of error, add to metrics. This is not required in case of success, as the
@@ -754,8 +780,7 @@
         if (result != Intents.RESULT_SMS_HANDLED && result != Activity.RESULT_OK) {
             mMetrics.writeIncomingSmsError(mPhone.getPhoneId(), is3gpp2(), smsSource, result);
             mPhone.getSmsStats().onIncomingSmsError(is3gpp2(), smsSource, result,
-                    TelephonyManager.from(mContext)
-                            .isEmergencyNumber(smsb.getOriginatingAddress()));
+                    isEmergencyNumber(smsb.getOriginatingAddress()));
             if (mPhone != null) {
                 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                 if (telephonyAnalytics != null) {
@@ -1034,7 +1059,7 @@
             logeWithLocalLog(errorMsg, tracker.getMessageId());
             mPhone.getSmsStats().onIncomingSmsError(
                     is3gpp2(), tracker.getSource(), RESULT_SMS_NULL_PDU,
-                    TelephonyManager.from(mContext).isEmergencyNumber(tracker.getAddress()));
+                    isEmergencyNumber(tracker.getAddress()));
             if (mPhone != null) {
                 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                 if (telephonyAnalytics != null) {
@@ -1064,8 +1089,7 @@
                                 tracker.getMessageId());
                         mPhone.getSmsStats().onIncomingSmsWapPush(tracker.getSource(),
                                 messageCount, RESULT_SMS_NULL_MESSAGE, tracker.getMessageId(),
-                                TelephonyManager.from(mContext)
-                                        .isEmergencyNumber(tracker.getAddress()));
+                                isEmergencyNumber(tracker.getAddress()));
                         return false;
                     }
                 }
@@ -1100,8 +1124,7 @@
             mMetrics.writeIncomingWapPush(mPhone.getPhoneId(), tracker.getSource(),
                     format, timestamps, wapPushResult, tracker.getMessageId());
             mPhone.getSmsStats().onIncomingSmsWapPush(tracker.getSource(), messageCount,
-                    result, tracker.getMessageId(), TelephonyManager.from(mContext)
-                            .isEmergencyNumber(tracker.getAddress()));
+                    result, tracker.getMessageId(), isEmergencyNumber(tracker.getAddress()));
             // result is Activity.RESULT_OK if an ordered broadcast was sent
             if (result == Activity.RESULT_OK) {
                 return true;
@@ -1122,7 +1145,7 @@
                 format, timestamps, block, tracker.getMessageId());
         mPhone.getSmsStats().onIncomingSmsSuccess(is3gpp2(), tracker.getSource(),
                 messageCount, block, tracker.getMessageId(),
-                TelephonyManager.from(mContext).isEmergencyNumber(tracker.getAddress()));
+                isEmergencyNumber(tracker.getAddress()));
         CarrierRoamingSatelliteSessionStats sessionStats =
                 CarrierRoamingSatelliteSessionStats.getInstance(mPhone.getSubId());
         sessionStats.onIncomingSms(mPhone.getSubId());
@@ -1160,6 +1183,13 @@
         return true;
     }
 
+    private boolean isEmergencyNumber(String number) {
+        if (!mPhone.hasCalling()) return false;
+        TelephonyManager manager = TelephonyManager.from(mContext);
+        if (manager == null) return false;
+        return manager.isEmergencyNumber(number);
+    }
+
     /**
      * Processes the message part while the credential-encrypted storage is still locked.
      *
@@ -1970,6 +2000,17 @@
         sendMessage(EVENT_BROADCAST_COMPLETE);
     }
 
+    private boolean isMtSmsPollingMessage(@NonNull SmsMessageBase smsb) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()
+                || !mContext.getResources().getBoolean(R.bool.config_enabled_mt_sms_polling)) {
+            return false;
+        }
+        String mtSmsPollingText = mContext.getResources()
+                .getString(R.string.config_mt_sms_polling_text);
+        return !TextUtils.isEmpty(mtSmsPollingText)
+                && mtSmsPollingText.equals(smsb.getMessageBody());
+    }
+
     /** Checks whether the flag to skip new message notification is set in the bitmask returned
      *  from the carrier app.
      */
diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index 3c35f1a..881adde 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -513,8 +513,8 @@
         SatelliteController satelliteController = SatelliteController.getInstance();
         boolean isSatelliteEnabledOrBeingEnabled = false;
         if (satelliteController != null) {
-            isSatelliteEnabledOrBeingEnabled = satelliteController.isSatelliteEnabled()
-                    || satelliteController.isSatelliteBeingEnabled();
+            isSatelliteEnabledOrBeingEnabled =
+                    satelliteController.isSatelliteEnabledOrBeingEnabled();
         }
 
         if (DBG) {
diff --git a/src/java/com/android/internal/telephony/NetworkResponse.java b/src/java/com/android/internal/telephony/NetworkResponse.java
index b4a37b3..ccf066f 100644
--- a/src/java/com/android/internal/telephony/NetworkResponse.java
+++ b/src/java/com/android/internal/telephony/NetworkResponse.java
@@ -551,6 +551,36 @@
         }
     }
 
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setSatellitePlmnResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
+     */
+    public void setSatelliteEnabledForCarrierResponse(RadioResponseInfo responseInfo) {
+        RadioResponse.responseVoid(HAL_SERVICE_NETWORK, mRil, responseInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error.
+     * @param isEnabled Indicates whether satellite is enabled for carrier or not.
+     */
+    public void isSatelliteEnabledForCarrierResponse(RadioResponseInfo responseInfo,
+            boolean isEnabled) {
+        RILRequest rr = mRil.processResponse(HAL_SERVICE_NETWORK, responseInfo);
+
+        if (rr != null) {
+            if (responseInfo.error == RadioError.NONE) {
+                RadioResponse.sendMessageResponse(rr.mResult, isEnabled);
+            }
+            mRil.processResponseDone(rr, responseInfo, isEnabled);
+        }
+    }
+
     @Override
     public String getInterfaceHash() {
         return IRadioNetworkResponse.HASH;
diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java
index 6a331cf..82e0c6e 100644
--- a/src/java/com/android/internal/telephony/NetworkTypeController.java
+++ b/src/java/com/android/internal/telephony/NetworkTypeController.java
@@ -22,7 +22,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
@@ -170,7 +176,6 @@
                 @Override
                 public void onQosSessionsChanged(
                         @NonNull List<QosBearerSession> qosBearerSessions) {
-                    if (!mIsTimerResetEnabledOnVoiceQos) return;
                     sendMessage(obtainMessage(EVENT_QOS_SESSION_CHANGED, qosBearerSessions));
                 }
 
@@ -233,6 +238,74 @@
     private int mLastAnchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
     private boolean mDoesPccListIndicateIdle = false;
 
+    private boolean mInVoiceCall = false;
+    private boolean mIsSatelliteConstrainedData = false;
+    private boolean mIsSatelliteNetworkCallbackRegistered = false;
+    private ConnectivityManager mConnectivityManager;
+
+    private final ConnectivityManager.NetworkCallback mNetworkCallback =
+            new ConnectivityManager.NetworkCallback() {
+                @Override
+                public void onAvailable(Network network) {
+                    log("On Available: " + network);
+                    if (network != null) {
+                        if (mConnectivityManager != null) {
+                            NetworkCapabilities capabilities =
+                                    mConnectivityManager.getNetworkCapabilities(network);
+                            updateBandwidthConstrainedStatus(capabilities);
+                        } else {
+                            log("network is null");
+                        }
+                    }
+                }
+
+                @Override
+                public void onCapabilitiesChanged(Network network,
+                        NetworkCapabilities networkCapabilities) {
+                    log("onCapabilitiesChanged: " + network);
+                    if (network != null) {
+                        updateBandwidthConstrainedStatus(networkCapabilities);
+                    } else {
+                        log("network is null");
+                    }
+                }
+
+                @Override
+                public void onLost(Network network) {
+                    log("Network Lost");
+                    if (mIsSatelliteConstrainedData) {
+                        mIsSatelliteConstrainedData = false;
+                        mDisplayInfoController.updateTelephonyDisplayInfo();
+                    }
+                }
+            };
+
+    private boolean isBandwidthConstrainedCapabilitySupported(NetworkCapabilities
+            capabilities) {
+        // TODO (b/382002908: Remove try catch exception for
+        //  NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED & replace datautils with
+        //  NetworkCapabilities on api availability at mainline module)
+        try {
+            return capabilities.hasTransport(
+                    NetworkCapabilities.TRANSPORT_SATELLITE) &&
+                    !capabilities.hasCapability(DataUtils.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+        } catch (Exception ignored) {
+            log("NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED not supported ");
+            return false;
+        }
+    }
+
+    private void updateBandwidthConstrainedStatus(NetworkCapabilities capabilities) {
+        if (capabilities != null) {
+            mIsSatelliteConstrainedData
+                    = isBandwidthConstrainedCapabilitySupported(capabilities);
+            log("satellite constrained data status : " + mIsSatelliteConstrainedData);
+            mDisplayInfoController.updateTelephonyDisplayInfo();
+        } else {
+            log("capabilities is null");
+        }
+    }
+
     /**
      * NetworkTypeController constructor.
      *
@@ -268,6 +341,35 @@
         sendMessage(EVENT_INITIALIZE);
     }
 
+    public synchronized void registerForSatelliteNetwork() {
+        if (!mIsSatelliteNetworkCallbackRegistered) {
+            mIsSatelliteNetworkCallbackRegistered = true;
+            HandlerThread handlerThread = new HandlerThread("SatelliteDataUsageThread");
+            handlerThread.start();
+            Handler handler = new Handler(handlerThread.getLooper());
+
+            NetworkRequest.Builder builder = new NetworkRequest.Builder();
+            builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+            // TODO (b/382002908: Remove try catch exception for
+            //  NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED & replace datautils with
+            //  NetworkCapabilities on api availability at mainline module)
+            try {
+                builder.removeCapability(DataUtils.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+            } catch (Exception ignored) {
+                log("NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED not supported ");
+            }
+            mConnectivityManager =
+                    (ConnectivityManager) mPhone.getContext()
+                            .getSystemService(Context.CONNECTIVITY_SERVICE);
+            if (mConnectivityManager != null) {
+                mConnectivityManager.registerBestMatchingNetworkCallback(
+                        builder.build(), mNetworkCallback, handler);
+            } else {
+                loge("network callback not registered");
+            }
+        }
+    }
+
     /**
      * @return The current override network type, used to create TelephonyDisplayInfo in
      * DisplayInfoController.
@@ -288,6 +390,15 @@
     }
 
     /**
+     * @return satellite bandwidth constrained connection status, used to create
+     * TelephonyDisplayInfo in DisplayInfoController.
+     *
+     */
+    public boolean getSatelliteConstrainedData() {
+       return mIsSatelliteConstrainedData;
+    }
+
+    /**
      * @return {@code true} if either the primary or secondary 5G icon timers are active,
      * and {@code false} if neither are.
      */
@@ -714,21 +825,22 @@
                     break;
                 case EVENT_QOS_SESSION_CHANGED:
                     List<QosBearerSession> qosBearerSessions = (List<QosBearerSession>) msg.obj;
-                    boolean inVoiceCall = false;
+                    mInVoiceCall = false;
                     for (QosBearerSession session : qosBearerSessions) {
                         // TS 23.203 23.501 - 1 means conversational voice
-                        if (session.getQos() instanceof EpsQos qos) {
-                            inVoiceCall = qos.getQci() == 1;
-                        } else if (session.getQos() instanceof NrQos qos) {
-                            inVoiceCall = qos.get5Qi() == 1;
-                        }
-                        if (inVoiceCall) {
-                            if (DBG) log("Device in voice call, reset all timers");
-                            resetAllTimers();
-                            transitionToCurrentState();
+                        if (session.getQos() instanceof EpsQos qos && qos.getQci() == 1) {
+                            mInVoiceCall = true;
+                            break;
+                        } else if (session.getQos() instanceof NrQos qos && qos.get5Qi() == 1) {
+                            mInVoiceCall = true;
                             break;
                         }
                     }
+                    if (mIsTimerResetEnabledOnVoiceQos && mInVoiceCall) {
+                        if (DBG) log("Device in voice call, reset all timers");
+                        resetAllTimers();
+                        transitionToCurrentState();
+                    }
                     break;
                 default:
                     throw new RuntimeException("Received invalid event: " + msg.what);
@@ -1371,6 +1483,8 @@
         if (mIsPrimaryTimerActive) {
             log("Transition without timer from " + getCurrentState().getName() + " to " + destName
                     + " due to existing " + mPrimaryTimerState + " primary timer.");
+        } else if (mIsTimerResetEnabledOnVoiceQos && mInVoiceCall) {
+            log("Skip primary timer to " + destName + " due to in call");
         } else {
             if (DBG) {
                 log("Transition with primary timer from " + mPreviousState + " to " + destName);
@@ -1395,7 +1509,10 @@
             log("Transition with secondary timer from " + currentName + " to "
                     + destState.getName());
         }
-        if (!mIsDeviceIdleMode && rule != null && rule.getSecondaryTimer(currentName) > 0) {
+        if (mIsTimerResetEnabledOnVoiceQos && mInVoiceCall) {
+            log("Skip secondary timer from " + currentName + " to "
+                    + destState.getName() + " due to in call");
+        } else if (!mIsDeviceIdleMode && rule != null && rule.getSecondaryTimer(currentName) > 0) {
             int duration = rule.getSecondaryTimer(currentName);
             if (mLastShownNrDueToAdvancedBand && mNrAdvancedBandsSecondaryTimer > 0) {
                 duration = mNrAdvancedBandsSecondaryTimer;
@@ -1754,6 +1871,7 @@
         pw.println("mPrimaryCellChangedWhileIdle=" + mPrimaryCellChangedWhileIdle);
         pw.println("mEnableNrAdvancedWhileRoaming=" + mEnableNrAdvancedWhileRoaming);
         pw.println("mIsDeviceIdleMode=" + mIsDeviceIdleMode);
+        pw.println("mIsTimerResetEnabledOnVoiceQos=" + mIsTimerResetEnabledOnVoiceQos);
         pw.decreaseIndent();
         pw.flush();
     }
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index 69b19aa..0c645a0 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -74,6 +74,7 @@
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.telephony.satellite.NtnSignalStrength;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Log;
@@ -126,6 +127,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -659,7 +661,7 @@
         mSmsStorageMonitor = mTelephonyComponentFactory.inject(SmsStorageMonitor.class.getName())
                 .makeSmsStorageMonitor(this, mFeatureFlags);
         mSmsUsageMonitor = mTelephonyComponentFactory.inject(SmsUsageMonitor.class.getName())
-                .makeSmsUsageMonitor(context);
+                .makeSmsUsageMonitor(context, mFeatureFlags);
         mUiccController = UiccController.getInstance();
         mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);
         mSimActivationTracker = mTelephonyComponentFactory
@@ -1965,6 +1967,13 @@
     }
 
     /**
+     * @return true if this device supports calling, false otherwise.
+     */
+    public boolean hasCalling() {
+        return TelephonyCapabilities.supportsTelephonyCalling(mFeatureFlags, mContext);
+    }
+
+    /**
      * Retrieves the EmergencyNumberTracker of the phone instance.
      */
     public EmergencyNumberTracker getEmergencyNumberTracker() {
@@ -2275,6 +2284,7 @@
      * @param response is callback message to report one of TelephonyManager#CDMA_ROAMING_MODE_*
      */
     public void queryCdmaRoamingPreference(Message response) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.queryCdmaRoamingPreference(response);
     }
 
@@ -2284,6 +2294,7 @@
      * @param response is callback message to report one of TelephonyManager#CDMA_SUBSCRIPTION_*
      */
     public void queryCdmaSubscriptionMode(Message response) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.getCdmaSubscriptionSource(response);
     }
 
@@ -2321,6 +2332,7 @@
      * @param response is callback message
      */
     public void setCdmaRoamingPreference(int cdmaRoamingType, Message response) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.setCdmaRoamingPreference(cdmaRoamingType, response);
     }
 
@@ -2330,6 +2342,7 @@
      * @param response is callback message
      */
     public void setCdmaSubscriptionMode(int cdmaSubscriptionType, Message response) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.setCdmaSubscriptionSource(cdmaSubscriptionType, response);
     }
 
@@ -2766,6 +2779,7 @@
      * @param workSource calling WorkSource
      */
     public void nvReadItem(int itemID, Message response, WorkSource workSource) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.nvReadItem(itemID, response, workSource);
     }
 
@@ -2780,6 +2794,7 @@
      */
     public void nvWriteItem(int itemID, String itemValue, Message response,
             WorkSource workSource) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.nvWriteItem(itemID, itemValue, response, workSource);
     }
 
@@ -2791,6 +2806,7 @@
      * @param response Callback message.
      */
     public void nvWriteCdmaPrl(byte[] preferredRoamingList, Message response) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.nvWriteCdmaPrl(preferredRoamingList, response);
     }
 
@@ -2812,6 +2828,7 @@
      * @param response Callback message.
      */
     public void resetModemConfig(Message response) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.nvResetConfig(3 /* factory NV reset */, response);
     }
 
@@ -2821,6 +2838,7 @@
      * @param response Callback message.
      */
     public void eraseModemConfig(Message response) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.nvResetConfig(2 /* erase NV */, response);
     }
 
@@ -3502,6 +3520,7 @@
      * @param obj User object.
      */
     public void registerForNumberInfo(Handler h, int what, Object obj) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.registerForNumberInfo(h, what, obj);
     }
 
@@ -3512,6 +3531,7 @@
      * @param h Handler to be removed from the registrant list.
      */
     public void unregisterForNumberInfo(Handler h) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.unregisterForNumberInfo(h);
     }
 
@@ -3527,6 +3547,7 @@
      * @param obj User object.
      */
     public void registerForRedirectedNumberInfo(Handler h, int what, Object obj) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.registerForRedirectedNumberInfo(h, what, obj);
     }
 
@@ -3537,6 +3558,7 @@
      * @param h Handler to be removed from the registrant list.
      */
     public void unregisterForRedirectedNumberInfo(Handler h) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.unregisterForRedirectedNumberInfo(h);
     }
 
@@ -3552,6 +3574,7 @@
      * @param obj User object.
      */
     public void registerForLineControlInfo(Handler h, int what, Object obj) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.registerForLineControlInfo(h, what, obj);
     }
 
@@ -3562,6 +3585,7 @@
      * @param h Handler to be removed from the registrant list.
      */
     public void unregisterForLineControlInfo(Handler h) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.unregisterForLineControlInfo(h);
     }
 
@@ -3577,6 +3601,7 @@
      * @param obj User object.
      */
     public void registerFoT53ClirlInfo(Handler h, int what, Object obj) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.registerFoT53ClirlInfo(h, what, obj);
     }
 
@@ -3587,6 +3612,7 @@
      * @param h Handler to be removed from the registrant list.
      */
     public void unregisterForT53ClirInfo(Handler h) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.unregisterForT53ClirInfo(h);
     }
 
@@ -3602,6 +3628,7 @@
      * @param obj User object.
      */
     public void registerForT53AudioControlInfo(Handler h, int what, Object obj) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.registerForT53AudioControlInfo(h, what, obj);
     }
 
@@ -3612,6 +3639,7 @@
      * @param h Handler to be removed from the registrant list.
      */
     public void unregisterForT53AudioControlInfo(Handler h) {
+        if (mFeatureFlags.cleanupCdma()) return;
         mCi.unregisterForT53AudioControlInfo(h);
     }
 
@@ -4067,6 +4095,16 @@
     }
 
     /**
+     * Resets the Carrier Keys in the database. This involves 2 steps:
+     * 1. Delete the keys from the database.
+     * 2. Send an intent to download new Certificates.
+     *
+     * @param forceResetAll : Force delete the downloaded key if any.
+     */
+    public void resetCarrierKeysForImsiEncryption(boolean forceResetAll) {
+    }
+
+    /**
      * Return if UT capability of ImsPhone is enabled or not
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -4140,8 +4178,11 @@
 
         setRoamingOverrideHelper(gsmRoamingList, GSM_ROAMING_LIST_OVERRIDE_PREFIX, iccId);
         setRoamingOverrideHelper(gsmNonRoamingList, GSM_NON_ROAMING_LIST_OVERRIDE_PREFIX, iccId);
-        setRoamingOverrideHelper(cdmaRoamingList, CDMA_ROAMING_LIST_OVERRIDE_PREFIX, iccId);
-        setRoamingOverrideHelper(cdmaNonRoamingList, CDMA_NON_ROAMING_LIST_OVERRIDE_PREFIX, iccId);
+        if (!mFeatureFlags.cleanupCdma()) {
+            setRoamingOverrideHelper(cdmaRoamingList, CDMA_ROAMING_LIST_OVERRIDE_PREFIX, iccId);
+            setRoamingOverrideHelper(cdmaNonRoamingList, CDMA_NON_ROAMING_LIST_OVERRIDE_PREFIX,
+                    iccId);
+        }
 
         // Refresh.
         ServiceStateTracker tracker = getServiceStateTracker();
@@ -5295,22 +5336,43 @@
     }
 
     /**
-     * Start callback mode
+     * Start the emergency callback mode
      * @param type for callback mode entry.
+     * @param durationMillis is the number of milliseconds remaining in the emergency callback
+     *                        mode.
      */
-    public void startCallbackMode(@TelephonyManager.EmergencyCallbackModeType int type) {
-        Rlog.d(mLogTag, "startCallbackMode:type=" + type);
-        mNotifier.notifyCallbackModeStarted(this, type);
+    public void startEmergencyCallbackMode(@TelephonyManager.EmergencyCallbackModeType int type,
+            long durationMillis) {
+        if (!mFeatureFlags.emergencyCallbackModeNotification()) return;
+
+        Rlog.d(mLogTag, "startEmergencyCallbackMode:type=" + type);
+        mNotifier.notifyCallbackModeStarted(this, type, durationMillis);
     }
 
     /**
-     * Stop callback mode
+     * Restart the emergency callback mode
+     * @param type for callback mode entry.
+     * @param durationMillis is the number of milliseconds remaining in the emergency callback
+     *                        mode.
+     */
+    public void restartEmergencyCallbackMode(@TelephonyManager.EmergencyCallbackModeType int type,
+            long durationMillis) {
+        if (!mFeatureFlags.emergencyCallbackModeNotification()) return;
+
+        Rlog.d(mLogTag, "restartEmergencyCallbackMode:type=" + type);
+        mNotifier.notifyCallbackModeRestarted(this, type, durationMillis);
+    }
+
+    /**
+     * Stop the emergency callback mode
      * @param type for callback mode exit.
      * @param reason for stopping callback mode.
      */
-    public void stopCallbackMode(@TelephonyManager.EmergencyCallbackModeType int type,
+    public void stopEmergencyCallbackMode(@TelephonyManager.EmergencyCallbackModeType int type,
             @TelephonyManager.EmergencyCallbackModeStopReason int reason) {
-        Rlog.d(mLogTag, "stopCallbackMode:type=" + type + ", reason=" + reason);
+        if (!mFeatureFlags.emergencyCallbackModeNotification()) return;
+
+        Rlog.d(mLogTag, "stopEmergencyCallbackMode:type=" + type + ", reason=" + reason);
         mNotifier.notifyCallbackModeStopped(this, type, reason);
     }
 
@@ -5347,6 +5409,29 @@
         mNotifier.notifyCarrierRoamingNtnEligibleStateChanged(this, eligible);
     }
 
+    /**
+     * Notify external listeners that carrier roaming non-terrestrial available services changed.
+     * @param availableServices The list of the supported services.
+     */
+    public void notifyCarrierRoamingNtnAvailableServicesChanged(
+            @NetworkRegistrationInfo.ServiceType int[] availableServices) {
+        logd("notifyCarrierRoamingNtnAvailableServicesChanged availableServices:"
+                + Arrays.toString(availableServices));
+        mNotifier.notifyCarrierRoamingNtnAvailableServicesChanged(this, availableServices);
+    }
+
+    /**
+     * Notify external listeners that carrier roaming non-terrestrial network
+     * signal strength changed.
+     * @param ntnSignalStrength non-terrestrial network signal strength.
+     */
+    public void notifyCarrierRoamingNtnSignalStrengthChanged(
+            @NonNull NtnSignalStrength ntnSignalStrength) {
+        logd("notifyCarrierRoamingNtnSignalStrengthChanged: ntnSignalStrength="
+                + ntnSignalStrength.getLevel());
+        mNotifier.notifyCarrierRoamingNtnSignalStrengthChanged(this, ntnSignalStrength);
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Phone: subId=" + getSubId());
         pw.println(" mPhoneId=" + mPhoneId);
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
index 3f388fb..6c8b222 100644
--- a/src/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.java
@@ -25,10 +25,13 @@
 import android.telephony.CallQuality;
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
+import android.telephony.CellularIdentifierDisclosure;
 import android.telephony.LinkCapacityEstimate;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseDataConnectionState;
+import android.telephony.SecurityAlgorithmUpdate;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager.DataEnabledReason;
@@ -37,6 +40,7 @@
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.NtnSignalStrength;
 
 import java.util.List;
 import java.util.Set;
@@ -145,7 +149,12 @@
             List<LinkCapacityEstimate> linkCapacityEstimateList);
 
     /** Notify callback mode started. */
-    void notifyCallbackModeStarted(Phone sender, @EmergencyCallbackModeType int type);
+    void notifyCallbackModeStarted(Phone sender, @EmergencyCallbackModeType int type,
+            long durationMillis);
+
+    /** Notify callback mode restarted. */
+    void notifyCallbackModeRestarted(Phone sender, @EmergencyCallbackModeType int type,
+            long durationMillis);
 
     /** Notify callback mode stopped. */
     void notifyCallbackModeStopped(Phone sender, @EmergencyCallbackModeType int type,
@@ -159,4 +168,19 @@
 
     /** Notify eligibility to connect to carrier roaming non-terrestrial network changed. */
     void notifyCarrierRoamingNtnEligibleStateChanged(Phone sender, boolean eligible);
+
+    /** Notify carrier roaming non-terrestrial available services changed. */
+    void notifyCarrierRoamingNtnAvailableServicesChanged(
+            Phone sender, @NetworkRegistrationInfo.ServiceType int[] availableServices);
+
+    /** Notify carrier roaming non-terrestrial network signal strength changed. */
+    void notifyCarrierRoamingNtnSignalStrengthChanged(Phone sender,
+            @NonNull NtnSignalStrength ntnSignalStrength);
+
+    /** Notify of a cellular identifier disclosure change. */
+    void notifyCellularIdentifierDisclosedChanged(Phone sender,
+            CellularIdentifierDisclosure disclosure);
+
+    /** Notify of a security algorithm update change. */
+    void notifySecurityAlgorithmsChanged(Phone sender, SecurityAlgorithmUpdate update);
 }
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoController.java b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
index 7ee3de2..9801542 100644
--- a/src/java/com/android/internal/telephony/PhoneSubInfoController.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
@@ -53,7 +53,9 @@
 import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Collectors;
 
 public class PhoneSubInfoController extends IPhoneSubInfo.Stub {
     private static final String TAG = "PhoneSubInfoController";
@@ -477,39 +479,37 @@
      *
      * @param subId subscriptionId
      * @param callingPackage package name of the caller
-     * @param callingFeatureId feature Id of the caller
      * @return List of public user identities of type android.net.Uri or empty list  if
      * EF_IMPU is not available.
      * @throws IllegalArgumentException if the subscriptionId is not valid
      * @throws IllegalStateException in case the ISIM hasn’t been loaded.
      * @throws SecurityException if the caller does not have the required permission
      */
-    public List<Uri> getImsPublicUserIdentities(int subId, String callingPackage,
-            String callingFeatureId) {
-        if (TelephonyPermissions.
-                checkCallingOrSelfReadPrivilegedPhoneStatePermissionOrReadPhoneNumber(
-                mContext, subId, callingPackage, callingFeatureId, "getImsPublicUserIdentities")) {
-
-            enforceTelephonyFeatureWithException(callingPackage,
-                    PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getImsPublicUserIdentities");
-
-            Phone phone = getPhone(subId);
-            assert phone != null;
-            IsimRecords isimRecords = phone.getIsimRecords();
-            if (isimRecords != null) {
-                String[] impus = isimRecords.getIsimImpu();
-                List<Uri> impuList = new ArrayList<>();
-                for (String impu : impus) {
-                    if (impu != null && impu.trim().length() > 0) {
-                        impuList.add(Uri.parse(impu));
-                    }
-                }
-                return impuList;
-            }
-            throw new IllegalStateException("ISIM is not loaded");
-        } else {
-            throw new IllegalArgumentException("Invalid SubscriptionID  = " + subId);
+    public List<Uri> getImsPublicUserIdentities(int subId, String callingPackage) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid subscription: " + subId);
         }
+
+        TelephonyPermissions
+                .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+                        mContext, subId, "getImsPublicUserIdentities");
+        enforceTelephonyFeatureWithException(callingPackage,
+                PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getImsPublicUserIdentities");
+
+        Phone phone = getPhone(subId);
+        assert phone != null;
+        IsimRecords isimRecords = phone.getIsimRecords();
+        if (isimRecords != null) {
+            String[] impus = isimRecords.getIsimImpu();
+            List<Uri> impuList = new ArrayList<>();
+            for (String impu : impus) {
+                if (impu != null && impu.trim().length() > 0) {
+                    impuList.add(Uri.parse(impu));
+                }
+            }
+            return impuList;
+        }
+        throw new IllegalStateException("ISIM is not loaded");
     }
 
     /**
@@ -546,6 +546,45 @@
     }
 
     /**
+     * Fetches the IMS Proxy Call Session Control Function(P-CSCF) based on the subscription.
+     *
+     * @param subId subscriptionId
+     * @param callingPackage package name of the caller
+     * @return List of IMS Proxy Call Session Control Function strings.
+     * @throws IllegalArgumentException if the subscriptionId is not valid
+     * @throws IllegalStateException in case the ISIM hasn’t been loaded.
+     * @throws SecurityException if the caller does not have the required permission
+     */
+    public List<String> getImsPcscfAddresses(int subId, String callingPackage) {
+        if (!mFeatureFlags.supportIsimRecord()) {
+            return new ArrayList<>();
+        }
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid subscription: " + subId);
+        }
+
+        TelephonyPermissions
+                .enforceCallingOrSelfReadPrivilegedPhoneStatePermissionOrCarrierPrivilege(
+                        mContext, subId, "getImsPcscfAddresses");
+        enforceTelephonyFeatureWithException(callingPackage,
+                PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION, "getImsPcscfAddresses");
+
+        Phone phone = getPhone(subId);
+        assert phone != null;
+        IsimRecords isimRecords = phone.getIsimRecords();
+        if (isimRecords != null) {
+            String[] pcscfs = isimRecords.getIsimPcscf();
+            List<String> pcscfList = Arrays.stream(pcscfs)
+                    .filter(u -> u != null)
+                    .map(u -> u.trim())
+                    .filter(u -> u.length() > 0)
+                    .collect(Collectors.toList());
+            return pcscfList;
+        }
+        throw new IllegalStateException("ISIM is not loaded");
+    }
+
+    /**
      * Returns the USIM service table that fetched from EFUST elementary field that are loaded
      * based on the appType.
      */
@@ -620,6 +659,20 @@
                 });
     }
 
+    /**
+     * Return GroupIdLevel2 for the subscriber
+     */
+    public String getGroupIdLevel2ForSubscriber(int subId, String callingPackage,
+            String callingFeatureId) {
+        return callPhoneMethodForSubIdWithPrivilegedCheck(subId,
+                "getGroupIdLevel2", (phone)-> {
+                    enforceTelephonyFeatureWithException(callingPackage,
+                            PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION,
+                            "getGroupIdLevel2ForSubscriber");
+                    return phone.getGroupIdLevel2();
+                });
+    }
+
     /** Below are utility methods that abstracts the flow that many public methods use:
      *  1. Check permission: pass, throw exception, or fails (returns false).
      *  2. clearCallingIdentity.
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index 88ead6f..39b6d37 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -178,6 +178,12 @@
     /** @hide */
     public static final HalVersion RADIO_HAL_VERSION_2_2 = new HalVersion(2, 2);
 
+    /** @hide */
+    public static final HalVersion RADIO_HAL_VERSION_2_3 = new HalVersion(2, 3);
+
+    /** @hide */
+    public static final HalVersion RADIO_HAL_VERSION_2_4 = new HalVersion(2, 4);
+
     // Hal version
     private final Map<Integer, HalVersion> mHalVersion = new HashMap<>();
 
@@ -1119,6 +1125,9 @@
             SparseArray<RadioServiceProxy> proxies, @NonNull FeatureFlags flags) {
         super(context);
         mFeatureFlags = flags;
+        if (mFeatureFlags.cleanupCdma()) {
+            cdmaSubscription = TelephonyManager.CDMA_SUBSCRIPTION_UNKNOWN;
+        }
         if (RILJ_LOGD) {
             riljLog("RIL: init allowedNetworkTypes=" + allowedNetworkTypes
                     + " cdmaSubscription=" + cdmaSubscription + ")");
@@ -3212,6 +3221,8 @@
 
     @Override
     public void setCdmaSubscriptionSource(int cdmaSubscription, Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
         if (!canMakeRequest("setCdmaSubscriptionSource", simProxy, result, RADIO_HAL_VERSION_1_4)) {
             return;
@@ -3232,6 +3243,8 @@
 
     @Override
     public void queryCdmaRoamingPreference(Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
         if (!canMakeRequest("queryCdmaRoamingPreference", networkProxy, result,
                 RADIO_HAL_VERSION_1_4)) {
@@ -3252,6 +3265,8 @@
 
     @Override
     public void setCdmaRoamingPreference(int cdmaRoamingType, Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
         if (!canMakeRequest("setCdmaRoamingPreference", networkProxy, result,
                 RADIO_HAL_VERSION_1_4)) {
@@ -3351,6 +3366,8 @@
 
     @Override
     public void sendCDMAFeatureCode(String featureCode, Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioVoiceProxy voiceProxy = getRadioServiceProxy(RadioVoiceProxy.class);
         if (!canMakeRequest("sendCDMAFeatureCode", voiceProxy, result, RADIO_HAL_VERSION_1_4)) {
             return;
@@ -3391,6 +3408,8 @@
 
     @Override
     public void sendCdmaSMSExpectMore(byte[] pdu, Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
         if (!canMakeRequest("sendCdmaSMSExpectMore", messagingProxy, result,
                 RADIO_HAL_VERSION_1_4)) {
@@ -3417,6 +3436,8 @@
 
     @Override
     public void sendCdmaSms(byte[] pdu, Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
         if (!canMakeRequest("sendCdmaSms", messagingProxy, result, RADIO_HAL_VERSION_1_4)) {
             return;
@@ -3438,6 +3459,8 @@
 
     @Override
     public void acknowledgeLastIncomingCdmaSms(boolean success, int cause, Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
         if (!canMakeRequest("acknowledgeLastIncomingCdmaSms", messagingProxy, result,
                 RADIO_HAL_VERSION_1_4)) {
@@ -3525,6 +3548,8 @@
 
     @Override
     public void getCdmaBroadcastConfig(Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
         if (!canMakeRequest("getCdmaBroadcastConfig", messagingProxy, result,
                 RADIO_HAL_VERSION_1_4)) {
@@ -3545,6 +3570,8 @@
 
     @Override
     public void setCdmaBroadcastConfig(CdmaSmsBroadcastConfigInfo[] configs, Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
         if (!canMakeRequest("setCdmaBroadcastConfig", messagingProxy, result,
                 RADIO_HAL_VERSION_1_4)) {
@@ -3569,6 +3596,8 @@
 
     @Override
     public void setCdmaBroadcastActivation(boolean activate, Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
         if (!canMakeRequest("setCdmaBroadcastActivation", messagingProxy, result,
                 RADIO_HAL_VERSION_1_4)) {
@@ -3590,6 +3619,8 @@
 
     @Override
     public void getCDMASubscription(Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
         if (!canMakeRequest("getCDMASubscription", simProxy, result, RADIO_HAL_VERSION_1_4)) {
             return;
@@ -3628,6 +3659,8 @@
 
     @Override
     public void deleteSmsOnRuim(int index, Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioMessagingProxy messagingProxy = getRadioServiceProxy(RadioMessagingProxy.class);
         if (!canMakeRequest("deleteSmsOnRuim", messagingProxy, result, RADIO_HAL_VERSION_1_4)) {
             return;
@@ -3781,6 +3814,8 @@
 
     @Override
     public void getCdmaSubscriptionSource(Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioSimProxy simProxy = getRadioServiceProxy(RadioSimProxy.class);
         if (!canMakeRequest("getCdmaSubscriptionSource", simProxy, result, RADIO_HAL_VERSION_1_4)) {
             return;
@@ -4068,6 +4103,8 @@
 
     @Override
     public void nvReadItem(int itemID, Message result, WorkSource workSource) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
         if (!canMakeRequest("nvReadItem", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
             return;
@@ -4088,6 +4125,8 @@
 
     @Override
     public void nvWriteItem(int itemId, String itemValue, Message result, WorkSource workSource) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
         if (!canMakeRequest("nvWriteItem", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
             return;
@@ -4109,6 +4148,8 @@
 
     @Override
     public void nvWriteCdmaPrl(byte[] preferredRoamingList, Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
         if (!canMakeRequest("nvWriteCdmaPrl", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
             return;
@@ -4129,6 +4170,8 @@
 
     @Override
     public void nvResetConfig(int resetType, Message result) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         RadioModemProxy modemProxy = getRadioServiceProxy(RadioModemProxy.class);
         if (!canMakeRequest("nvResetConfig", modemProxy, result, RADIO_HAL_VERSION_1_4)) {
             return;
@@ -5349,6 +5392,102 @@
             });
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setSatellitePlmn(int simSlot, @NonNull List<String> carrierPlmnList,
+            @NonNull List<String> allSatellitePlmnList, Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest(
+                "setSatellitePlmn",
+                networkProxy,
+                result,
+                RADIO_HAL_VERSION_2_4)) {
+            return;
+        }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_SATELLITE_PLMN, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " simSlot=" + simSlot + " carrierPlmnList=" + carrierPlmnList
+                    + " allSatellitePlmnList=" + allSatellitePlmnList);
+        }
+
+        radioServiceInvokeHelper(
+                HAL_SERVICE_NETWORK,
+                rr,
+                "setSatellitePlmn",
+                () -> {
+                    networkProxy.setSatellitePlmn(rr.mSerial, simSlot, carrierPlmnList,
+                            allSatellitePlmnList);
+                });
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setSatelliteEnabledForCarrier(int simSlot, boolean satelliteEnabled,
+            Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest(
+                "setSatelliteEnabledForCarrier",
+                networkProxy,
+                result,
+                RADIO_HAL_VERSION_2_4)) {
+            return;
+        }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_SET_SATELLITE_ENABLED_FOR_CARRIER, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " simSlot=" + simSlot + " satelliteEnabled=" + satelliteEnabled);
+        }
+
+        radioServiceInvokeHelper(
+                HAL_SERVICE_NETWORK,
+                rr,
+                "setSatelliteEnabledForCarrier",
+                () -> {
+                    networkProxy.setSatelliteEnabledForCarrier(rr.mSerial, simSlot,
+                            satelliteEnabled);
+                });
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void isSatelliteEnabledForCarrier(int simSlot, Message result) {
+        RadioNetworkProxy networkProxy = getRadioServiceProxy(RadioNetworkProxy.class);
+        if (!canMakeRequest(
+                "isSatelliteEnabledForCarrier",
+                networkProxy,
+                result,
+                RADIO_HAL_VERSION_2_4)) {
+            return;
+        }
+
+        RILRequest rr = obtainRequest(RIL_REQUEST_IS_SATELLITE_ENABLED_FOR_CARRIER, result,
+                mRILDefaultWorkSource);
+
+        if (RILJ_LOGD) {
+            riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
+                    + " simSlot=" + simSlot);
+        }
+
+        radioServiceInvokeHelper(
+                HAL_SERVICE_NETWORK, rr, "isSatelliteEnabledForCarrier", () -> {
+                    networkProxy.isSatelliteEnabledForCarrier(rr.mSerial, simSlot);
+                });
+    }
+
+
     //***** Private Methods
     /**
      * This is a helper function to be called when an indication callback is called for any radio
@@ -6016,6 +6155,8 @@
 
     @UnsupportedAppUsage
     void notifyRegistrantsCdmaInfoRec(CdmaInformationRecords infoRec) {
+        if (mFeatureFlags.cleanupCdma()) return;
+
         int response = RIL_UNSOL_CDMA_INFO_REC;
         if (infoRec.record instanceof CdmaInformationRecords.CdmaDisplayInfoRec) {
             if (mDisplayInfoRegistrants != null) {
@@ -6212,6 +6353,8 @@
             case 1: return RADIO_HAL_VERSION_2_0;
             case 2: return RADIO_HAL_VERSION_2_1;
             case 3: return RADIO_HAL_VERSION_2_2;
+            case 4: return RADIO_HAL_VERSION_2_3;
+            case 5: return RADIO_HAL_VERSION_2_4;
             default: return RADIO_HAL_VERSION_UNKNOWN;
         }
     }
diff --git a/src/java/com/android/internal/telephony/RILUtils.java b/src/java/com/android/internal/telephony/RILUtils.java
index a81dbc8..15e2374 100644
--- a/src/java/com/android/internal/telephony/RILUtils.java
+++ b/src/java/com/android/internal/telephony/RILUtils.java
@@ -121,6 +121,7 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_N1_MODE_ENABLED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_NR_DUAL_CONNECTIVITY_ENABLED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_SATELLITE_ENABLED_FOR_CARRIER;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_SECURITY_ALGORITHMS_UPDATED_ENABLED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_IS_VONR_ENABLED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_LAST_CALL_FAIL_CAUSE;
@@ -180,6 +181,8 @@
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_PREFERRED_DATA_MODEM;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_RADIO_CAPABILITY;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SATELLITE_ENABLED_FOR_CARRIER;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SATELLITE_PLMN;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA;
 import static com.android.internal.telephony.RILConstants.RIL_REQUEST_SET_SIM_CARD_POWER;
@@ -384,6 +387,7 @@
 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
 import com.android.internal.telephony.data.KeepaliveStatus;
 import com.android.internal.telephony.data.KeepaliveStatus.KeepaliveStatusCode;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.imsphone.ImsCallInfo;
 import com.android.internal.telephony.uicc.AdnCapacity;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus;
@@ -394,6 +398,7 @@
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.PortUtils;
 import com.android.internal.telephony.uicc.SimPhonebookRecord;
+import com.android.internal.telephony.uicc.SimTypeInfo;
 import com.android.telephony.Rlog;
 
 import java.io.ByteArrayInputStream;
@@ -1169,6 +1174,10 @@
          * 2 - erase NV reset (SCRTN)
          * 3 - factory reset (RTN)
          */
+        if (Flags.cleanupCdma()) {
+            if (resetType == 1) return android.hardware.radio.V1_0.ResetNvType.RELOAD;
+            return -1;
+        }
         switch (resetType) {
             case 1: return android.hardware.radio.V1_0.ResetNvType.RELOAD;
             case 2: return android.hardware.radio.V1_0.ResetNvType.ERASE;
@@ -1189,6 +1198,10 @@
          * 2 - erase NV reset (SCRTN)
          * 3 - factory reset (RTN)
          */
+        if (Flags.cleanupCdma()) {
+            if (resetType == 1) return android.hardware.radio.modem.ResetNvType.RELOAD;
+            return -1;
+        }
         switch (resetType) {
             case 1: return android.hardware.radio.modem.ResetNvType.RELOAD;
             case 2: return android.hardware.radio.modem.ResetNvType.ERASE;
@@ -1685,6 +1698,9 @@
         if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_NR) != 0) {
             raf |= android.hardware.radio.RadioAccessFamily.NR;
         }
+        if ((networkTypeBitmask & TelephonyManager.NETWORK_TYPE_BITMASK_NB_IOT_NTN) != 0) {
+            raf |= android.hardware.radio.RadioAccessFamily.NB_IOT_NTN;
+        }
         return (raf == 0) ? android.hardware.radio.RadioAccessFamily.UNKNOWN : raf;
     }
 
@@ -2913,6 +2929,7 @@
      */
     public static CellSignalStrengthGsm convertHalGsmSignalStrength(
             android.hardware.radio.V1_0.GsmSignalStrength ss) {
+        if (ss == null) return new CellSignalStrengthGsm();
         CellSignalStrengthGsm ret = new CellSignalStrengthGsm(
                 CellSignalStrength.getRssiDbmFromAsu(ss.signalStrength), ss.bitErrorRate,
                 ss.timingAdvance);
@@ -2930,6 +2947,7 @@
      */
     public static CellSignalStrengthGsm convertHalGsmSignalStrength(
             android.hardware.radio.network.GsmSignalStrength ss) {
+        if (ss == null) return new CellSignalStrengthGsm();
         CellSignalStrengthGsm ret = new CellSignalStrengthGsm(
                 CellSignalStrength.getRssiDbmFromAsu(ss.signalStrength), ss.bitErrorRate,
                 ss.timingAdvance);
@@ -2950,6 +2968,7 @@
     public static CellSignalStrengthCdma convertHalCdmaSignalStrength(
             android.hardware.radio.V1_0.CdmaSignalStrength cdma,
             android.hardware.radio.V1_0.EvdoSignalStrength evdo) {
+        if (cdma == null || evdo == null) return new CellSignalStrengthCdma();
         return new CellSignalStrengthCdma(-cdma.dbm, -cdma.ecio, -evdo.dbm, -evdo.ecio,
                 evdo.signalNoiseRatio);
     }
@@ -2964,6 +2983,7 @@
     public static CellSignalStrengthCdma convertHalCdmaSignalStrength(
             android.hardware.radio.network.CdmaSignalStrength cdma,
             android.hardware.radio.network.EvdoSignalStrength evdo) {
+        if (cdma == null || evdo == null) return new CellSignalStrengthCdma();
         return new CellSignalStrengthCdma(-cdma.dbm, -cdma.ecio, -evdo.dbm, -evdo.ecio,
                 evdo.signalNoiseRatio);
     }
@@ -3427,9 +3447,11 @@
             android.hardware.radio.data.SetupDataCallResult result) {
         if (result == null) return null;
         List<LinkAddress> laList = new ArrayList<>();
-        for (android.hardware.radio.data.LinkAddress la : result.addresses) {
-            laList.add(convertToLinkAddress(la.address, la.addressProperties,
-                    la.deprecationTime, la.expirationTime));
+        if (result.addresses != null) {
+            for (android.hardware.radio.data.LinkAddress la : result.addresses) {
+                laList.add(convertToLinkAddress(la.address, la.addressProperties,
+                        la.deprecationTime, la.expirationTime));
+            }
         }
         List<InetAddress> dnsList = new ArrayList<>();
         if (result.dnses != null) {
@@ -3471,15 +3493,19 @@
             }
         }
         List<QosBearerSession> qosSessions = new ArrayList<>();
-        for (android.hardware.radio.data.QosSession session : result.qosSessions) {
-            qosSessions.add(convertHalQosBearerSession(session));
+        if (result.qosSessions != null) {
+            for (android.hardware.radio.data.QosSession session : result.qosSessions) {
+                qosSessions.add(convertHalQosBearerSession(session));
+            }
         }
         List<TrafficDescriptor> trafficDescriptors = new ArrayList<>();
-        for (android.hardware.radio.data.TrafficDescriptor td : result.trafficDescriptors) {
-            try {
-                trafficDescriptors.add(convertHalTrafficDescriptor(td));
-            } catch (IllegalArgumentException e) {
-                loge("convertHalDataCallResult: Failed to convert traffic descriptor. e=" + e);
+        if (result.trafficDescriptors != null) {
+            for (android.hardware.radio.data.TrafficDescriptor td : result.trafficDescriptors) {
+                try {
+                    trafficDescriptors.add(convertHalTrafficDescriptor(td));
+                } catch (IllegalArgumentException e) {
+                    loge("convertHalDataCallResult: Failed to convert traffic descriptor. e=" + e);
+                }
             }
         }
 
@@ -3659,6 +3685,7 @@
     }
 
     private static Qos convertHalQos(android.hardware.radio.V1_6.Qos qos) {
+        if (qos == null) return null;
         switch (qos.getDiscriminator()) {
             case android.hardware.radio.V1_6.Qos.hidl_discriminator.eps:
                 android.hardware.radio.V1_6.EpsQos eps = qos.eps();
@@ -3674,6 +3701,7 @@
     }
 
     private static Qos convertHalQos(android.hardware.radio.data.Qos qos) {
+        if (qos == null) return null;
         switch (qos.getTag()) {
             case android.hardware.radio.data.Qos.eps:
                 android.hardware.radio.data.EpsQos eps = qos.getEps();
@@ -4132,6 +4160,34 @@
     }
 
     /**
+     * This API is for fallback to support getAllowedCarriers too.
+     *
+     * Convert an array of CarrierInfo defined in
+     * radio/aidl/android/hardware/radio/sim/CarrierInfo.aidl to a list of CarrierIdentifiers.
+     *
+     * @param carrierInfos array of CarrierInfo defined in
+     *                     radio/aidl/android/hardware/radio/sim/CarrierInfo.aidl
+     * @return The converted list of CarrierIdentifiers
+     */
+    public static List<CarrierIdentifier> convertAidlCarrierInfoListToHalCarrierList(
+            android.hardware.radio.sim.CarrierInfo[] carrierInfos) {
+        List<CarrierIdentifier> ret = new ArrayList<>();
+        if (carrierInfos == null) {
+            return ret;
+        }
+        for (android.hardware.radio.sim.CarrierInfo carrierInfo : carrierInfos) {
+            String mcc = carrierInfo.mcc;
+            String mnc = carrierInfo.mnc;
+            String spn = carrierInfo.spn;
+            String imsi = carrierInfo.imsiPrefix;
+            String gid1 = carrierInfo.gid1;
+            String gid2 = carrierInfo.gid2;
+            ret.add(new CarrierIdentifier(mcc, mnc, spn, imsi, gid1, gid2));
+        }
+        return ret;
+    }
+
+    /**
      * Convert the sim policy defined in
      * radio/aidl/android/hardware/radio/sim/SimLockMultiSimPolicy.aidl to the equivalent sim
      * policy defined in android.telephony/CarrierRestrictionRules.MultiSimPolicy
@@ -4191,7 +4247,8 @@
             iccCardStatus.setCardState(cardStatus10.cardState);
             iccCardStatus.setUniversalPinState(cardStatus10.universalPinState);
             iccCardStatus.mGsmUmtsSubscriptionAppIndex = cardStatus10.gsmUmtsSubscriptionAppIndex;
-            iccCardStatus.mCdmaSubscriptionAppIndex = cardStatus10.cdmaSubscriptionAppIndex;
+            iccCardStatus.mCdmaSubscriptionAppIndex =
+                    Flags.cleanupCdma() ? -1 : cardStatus10.cdmaSubscriptionAppIndex;
             iccCardStatus.mImsSubscriptionAppIndex = cardStatus10.imsSubscriptionAppIndex;
             int numApplications = cardStatus10.applications.size();
 
@@ -4261,7 +4318,8 @@
         iccCardStatus.setMultipleEnabledProfilesMode(cardStatus.supportedMepMode);
         iccCardStatus.setUniversalPinState(cardStatus.universalPinState);
         iccCardStatus.mGsmUmtsSubscriptionAppIndex = cardStatus.gsmUmtsSubscriptionAppIndex;
-        iccCardStatus.mCdmaSubscriptionAppIndex = cardStatus.cdmaSubscriptionAppIndex;
+        iccCardStatus.mCdmaSubscriptionAppIndex =
+                Flags.cleanupCdma() ? -1 : cardStatus.cdmaSubscriptionAppIndex;
         iccCardStatus.mImsSubscriptionAppIndex = cardStatus.imsSubscriptionAppIndex;
         iccCardStatus.atr = cardStatus.atr;
         iccCardStatus.iccid = cardStatus.iccid;
@@ -5260,6 +5318,12 @@
                 return "IS_SECURITY_ALGORITHMS_UPDATED_ENABLED";
             case RIL_REQUEST_GET_SIMULTANEOUS_CALLING_SUPPORT:
                 return "GET_SIMULTANEOUS_CALLING_SUPPORT";
+            case RIL_REQUEST_SET_SATELLITE_PLMN:
+                return "SET_SATELLITE_PLMN";
+            case RIL_REQUEST_SET_SATELLITE_ENABLED_FOR_CARRIER:
+                return "SET_SATELLITE_ENABLED_FOR_CARRIER";
+            case RIL_REQUEST_IS_SATELLITE_ENABLED_FOR_CARRIER:
+                return "IS_SATELLITE_ENABLED_FOR_CARRIER";
             default:
                 return "<unknown request " + request + ">";
         }
@@ -5842,6 +5906,23 @@
                 securityAlgorithmUpdate.isUnprotectedEmergency);
     }
 
+    /** Convert an AIDL-based SimTypeInfo to its Java wrapper. */
+    public static ArrayList<SimTypeInfo> convertAidlSimTypeInfo(
+            android.hardware.radio.config.SimTypeInfo[] simTypeInfos) {
+        ArrayList<SimTypeInfo> response = new ArrayList<>();
+        if (simTypeInfos == null) {
+            loge("convertAidlSimTypeInfo received NULL simTypeInfos");
+            return response;
+        }
+        for (android.hardware.radio.config.SimTypeInfo simTypeInfo : simTypeInfos) {
+            SimTypeInfo info = new SimTypeInfo();
+            info.mSupportedSimTypes = simTypeInfo.supportedSimTypes;
+            info.setCurrentSimType(simTypeInfo.currentSimType);
+            response.add(info);
+        }
+        return response;
+    }
+
     private static void logd(String log) {
         Rlog.d("RILUtils", log);
     }
diff --git a/src/java/com/android/internal/telephony/RadioConfigProxy.java b/src/java/com/android/internal/telephony/RadioConfigProxy.java
index 9f34e29..153747b 100644
--- a/src/java/com/android/internal/telephony/RadioConfigProxy.java
+++ b/src/java/com/android/internal/telephony/RadioConfigProxy.java
@@ -281,7 +281,11 @@
         }
 
         public void linkToDeath(long cookie) throws RemoteException {
-            mService.linkToDeath(this, cookie);
+            if (mService != null) {
+                mService.linkToDeath(this, cookie);
+            } else {
+                Rlog.w(TAG, "linkToDeath: skipping since mService is null");
+            }
         }
 
         public void clear() {
@@ -316,7 +320,11 @@
         }
 
         public void linkToDeath(int cookie) throws RemoteException {
-            mService.linkToDeath(this, cookie);
+            if (mService != null) {
+                mService.linkToDeath(this, cookie);
+            } else {
+                Rlog.w(TAG, "linkToDeath: skipping since mService is null");
+            }
         }
 
         public void clear() {
diff --git a/src/java/com/android/internal/telephony/RadioConfigResponseAidl.java b/src/java/com/android/internal/telephony/RadioConfigResponseAidl.java
index 0a41b11..6142fc2 100644
--- a/src/java/com/android/internal/telephony/RadioConfigResponseAidl.java
+++ b/src/java/com/android/internal/telephony/RadioConfigResponseAidl.java
@@ -21,10 +21,10 @@
 import android.telephony.PhoneCapability;
 
 import com.android.internal.telephony.uicc.IccSlotStatus;
+import com.android.internal.telephony.uicc.SimTypeInfo;
 import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Set;
 
 /**
@@ -47,8 +47,7 @@
      */
     @Override
     public void getHalDeviceCapabilitiesResponse(
-            android.hardware.radio.RadioResponseInfo info,
-            boolean modemReducedFeatureSet1) throws RemoteException {
+            RadioResponseInfo info, boolean modemReducedFeatureSet1) throws RemoteException {
         // convert hal device capabilities to RadioInterfaceCapabilities
         RILRequest rr = mRadioConfig.processResponse(info);
         if (rr != null) {
@@ -71,8 +70,7 @@
      */
     @Override
     public void getNumOfLiveModemsResponse(
-            android.hardware.radio.RadioResponseInfo info, byte numOfLiveModems)
-            throws RemoteException {
+            RadioResponseInfo info, byte numOfLiveModems) throws RemoteException {
         RILRequest rr = mRadioConfig.processResponse(info);
         if (rr != null) {
             if (info.error == android.hardware.radio.RadioError.NONE) {
@@ -93,9 +91,8 @@
      */
     @Override
     public void getPhoneCapabilityResponse(
-            android.hardware.radio.RadioResponseInfo info,
-            android.hardware.radio.config.PhoneCapability phoneCapability)
-            throws RemoteException {
+            RadioResponseInfo info,
+            android.hardware.radio.config.PhoneCapability phoneCapability) throws RemoteException {
         RILRequest rr = mRadioConfig.processResponse(info);
         if (rr != null) {
             PhoneCapability ret = RILUtils.convertHalPhoneCapability(
@@ -118,9 +115,7 @@
      */
     @Override
     public void getSimultaneousCallingSupportResponse(
-            android.hardware.radio.RadioResponseInfo info,
-            int[] enabledLogicalSlots)
-            throws RemoteException {
+            RadioResponseInfo info, int[] enabledLogicalSlots) throws RemoteException {
         RILRequest rr = mRadioConfig.processResponse(info);
         if (rr != null) {
             ArrayList<Integer> ret = RILUtils.primitiveArrayToArrayList(enabledLogicalSlots);
@@ -142,7 +137,7 @@
      */
     @Override
     public void getSimSlotsStatusResponse(
-            android.hardware.radio.RadioResponseInfo info,
+            RadioResponseInfo info,
             android.hardware.radio.config.SimSlotStatus[] slotStatus)
             throws RemoteException {
         RILRequest rr = mRadioConfig.processResponse(info);
@@ -166,8 +161,7 @@
      * Currently this is being used as the callback for RadioConfig.setNumOfLiveModems() method
      */
     @Override
-    public void setNumOfLiveModemsResponse(
-            android.hardware.radio.RadioResponseInfo info) throws RemoteException {
+    public void setNumOfLiveModemsResponse(RadioResponseInfo info) throws RemoteException {
         RILRequest rr = mRadioConfig.processResponse(info);
         if (rr != null) {
             if (info.error == android.hardware.radio.RadioError.NONE) {
@@ -187,8 +181,7 @@
      * Response function for IRadioConfig.setPreferredDataModem().
      */
     @Override
-    public void setPreferredDataModemResponse(
-            android.hardware.radio.RadioResponseInfo info) throws RemoteException {
+    public void setPreferredDataModemResponse(RadioResponseInfo info) throws RemoteException {
         RILRequest rr = mRadioConfig.processResponse(info);
         if (rr != null) {
             if (info.error == android.hardware.radio.RadioError.NONE) {
@@ -208,8 +201,7 @@
      * Response function for IRadioConfig.setSimSlotsMapping().
      */
     @Override
-    public void setSimSlotsMappingResponse(
-            android.hardware.radio.RadioResponseInfo info) throws RemoteException {
+    public void setSimSlotsMappingResponse(RadioResponseInfo info) throws RemoteException {
         RILRequest rr = mRadioConfig.processResponse(info);
         if (rr != null) {
             if (info.error == android.hardware.radio.RadioError.NONE) {
@@ -225,6 +217,48 @@
         }
     }
 
+    /**
+     * Response function for IRadioConfig.getSimTypeInfo().
+     */
+    @Override
+    public void getSimTypeInfoResponse(
+            RadioResponseInfo info,
+            android.hardware.radio.config.SimTypeInfo[] simTypeInfo) throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            ArrayList<SimTypeInfo> ret = RILUtils.convertAidlSimTypeInfo(simTypeInfo);
+            if (info.error == android.hardware.radio.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, ret);
+                logd(rr, RILUtils.requestToString(rr.mRequest) + " " + ret.toString());
+            } else {
+                rr.onError(info.error, null);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("getSimTypeInfoResponse: Error " + info.toString());
+        }
+    }
+
+    /**
+     * Response function for IRadioConfig.setSimTypeResponse().
+     */
+    @Override
+    public void setSimTypeResponse(RadioResponseInfo info) throws RemoteException {
+        RILRequest rr = mRadioConfig.processResponse(info);
+        if (rr != null) {
+            if (info.error == android.hardware.radio.RadioError.NONE) {
+                // send response
+                RadioResponse.sendMessageResponse(rr.mResult, null);
+                logd(rr, RILUtils.requestToString(rr.mRequest));
+            } else {
+                rr.onError(info.error, null);
+                loge(rr, RILUtils.requestToString(rr.mRequest) + " error " + info.error);
+            }
+        } else {
+            loge("setSimTypeResponse: Error " + info.toString());
+        }
+    }
     private static void logd(String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/RadioMessagingProxy.java b/src/java/com/android/internal/telephony/RadioMessagingProxy.java
index c652284..624c82d 100644
--- a/src/java/com/android/internal/telephony/RadioMessagingProxy.java
+++ b/src/java/com/android/internal/telephony/RadioMessagingProxy.java
@@ -20,6 +20,7 @@
 import android.telephony.Rlog;
 
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 
 import java.util.ArrayList;
@@ -107,6 +108,7 @@
      */
     public void acknowledgeLastIncomingCdmaSms(int serial, boolean success, int cause)
             throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             android.hardware.radio.messaging.CdmaSmsAck msg =
@@ -147,6 +149,7 @@
      * @throws RemoteException
      */
     public void deleteSmsOnRuim(int serial, int index) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             mMessagingProxy.deleteSmsOnRuim(serial, index);
@@ -176,6 +179,7 @@
      * @throws RemoteException
      */
     public void getCdmaBroadcastConfig(int serial) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             mMessagingProxy.getCdmaBroadcastConfig(serial);
@@ -248,6 +252,7 @@
      * @throws RemoteException
      */
     public void sendCdmaSms(int serial, byte[] pdu) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             mMessagingProxy.sendCdmaSms(serial, RILUtils.convertToHalCdmaSmsMessageAidl(pdu));
@@ -266,6 +271,7 @@
      * @throws RemoteException
      */
     public void sendCdmaSmsExpectMore(int serial, byte[] pdu) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             mMessagingProxy.sendCdmaSmsExpectMore(
@@ -378,6 +384,7 @@
      * @throws RemoteException
      */
     public void setCdmaBroadcastActivation(int serial, boolean activate) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             mMessagingProxy.setCdmaBroadcastActivation(serial, activate);
@@ -394,6 +401,7 @@
      */
     public void setCdmaBroadcastConfig(int serial, CdmaSmsBroadcastConfigInfo[] configs)
             throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             ArrayList<android.hardware.radio.messaging.CdmaBroadcastSmsConfigInfo> halConfigs =
@@ -513,6 +521,7 @@
      * @throws RemoteException
      */
     public void writeSmsToRuim(int serial, int status, byte[] pdu) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             android.hardware.radio.messaging.CdmaSmsWriteArgs args =
diff --git a/src/java/com/android/internal/telephony/RadioModemProxy.java b/src/java/com/android/internal/telephony/RadioModemProxy.java
index cdcbcc0..bc19d55 100644
--- a/src/java/com/android/internal/telephony/RadioModemProxy.java
+++ b/src/java/com/android/internal/telephony/RadioModemProxy.java
@@ -19,6 +19,8 @@
 import android.os.RemoteException;
 import android.telephony.Rlog;
 
+import com.android.internal.telephony.flags.Flags;
+
 /**
  * A holder for IRadioModem.
  * Use getAidl to get IRadioModem and call the AIDL implementations of the HAL APIs.
@@ -195,6 +197,7 @@
      * @throws RemoteException
      */
     public void nvReadItem(int serial, int itemId) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             mModemProxy.nvReadItem(serial, itemId);
@@ -210,6 +213,7 @@
      * @throws RemoteException
      */
     public void nvResetConfig(int serial, int resetType) throws RemoteException {
+        if (Flags.cleanupCdma() && resetType != 1) return;
         if (isEmpty()) return;
         if (isAidl()) {
             mModemProxy.nvResetConfig(serial, RILUtils.convertToHalResetNvTypeAidl(resetType));
@@ -225,6 +229,7 @@
      * @throws RemoteException
      */
     public void nvWriteCdmaPrl(int serial, byte[] prl) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             mModemProxy.nvWriteCdmaPrl(serial, prl);
@@ -241,6 +246,7 @@
      * @throws RemoteException
      */
     public void nvWriteItem(int serial, int itemId, String itemValue) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             android.hardware.radio.modem.NvWriteItem item =
diff --git a/src/java/com/android/internal/telephony/RadioNetworkProxy.java b/src/java/com/android/internal/telephony/RadioNetworkProxy.java
index 4acc71a..c4b6f76 100644
--- a/src/java/com/android/internal/telephony/RadioNetworkProxy.java
+++ b/src/java/com/android/internal/telephony/RadioNetworkProxy.java
@@ -28,6 +28,8 @@
 import android.telephony.Rlog;
 import android.telephony.SignalThresholdInfo;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -172,6 +174,7 @@
      * @throws RemoteException
      */
     public void getCdmaRoamingPreference(int serial) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             mNetworkProxy.getCdmaRoamingPreference(serial);
@@ -431,6 +434,7 @@
      * @throws RemoteException
      */
     public void setCdmaRoamingPreference(int serial, int cdmaRoamingType) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             mNetworkProxy.setCdmaRoamingPreference(serial, cdmaRoamingType);
@@ -979,4 +983,60 @@
         }
         // Only supported on AIDL.
     }
+
+   /**
+     * Set the non-terrestrial PLMN with lower priority than terrestrial networks.
+     *
+     * @param serial Serial number of request.
+     * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
+     *                this information to determine the relevant carrier.
+     * @param carrierPlmnList The list of roaming PLMN used for connecting to satellite networks
+     *                        supported by user subscription.
+     * @param allSatellitePlmnList Modem should use the allSatellitePlmnList to identify satellite
+     *                             PLMNs that are not supported by the carrier and make sure not to
+     *                             attach to them.
+     */
+    public void setSatellitePlmn(int serial, int simSlot, List<String> carrierPlmnList,
+            List<String> allSatellitePlmnList) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            String[] carrierPlmnArray = carrierPlmnList.toArray(new String[0]);
+            String[] allSatellitePlmnArray = allSatellitePlmnList.toArray(new String[0]);
+            mNetworkProxy.setSatellitePlmn(serial, simSlot, carrierPlmnArray,
+                    allSatellitePlmnArray);
+        }
+        // Only supported on AIDL.
+    }
+
+    /**
+     * Enable or disable satellite in the cellular modem associated with a carrier.
+     *
+     * @param serial Serial number of request.
+     * @param simSlot Indicates the SIM slot to which this API will be applied. The modem will use
+     *                this information to determine the relevant carrier.
+     * @param satelliteEnabled {@code true} to enable satellite, {@code false} to disable satellite.
+     */
+    public void setSatelliteEnabledForCarrier(int serial, int simSlot,
+            boolean satelliteEnabled) throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mNetworkProxy.setSatelliteEnabledForCarrier(serial, simSlot, satelliteEnabled);
+        }
+        // Only supported on AIDL.
+    }
+
+    /**
+     * Check whether satellite is enabled in the cellular modem associated with a carrier.
+     *
+     * @param serial Serial number of request.
+     * @param simSlot Indicates the SIM slot to which this API will be applied.
+     */
+    public void isSatelliteEnabledForCarrier(int serial, int simSlot)
+            throws RemoteException {
+        if (isEmpty()) return;
+        if (isAidl()) {
+            mNetworkProxy.isSatelliteEnabledForCarrier(serial, simSlot);
+        }
+        // Only supported on AIDL.
+    }
 }
diff --git a/src/java/com/android/internal/telephony/RadioSimProxy.java b/src/java/com/android/internal/telephony/RadioSimProxy.java
index 1c864fe..9316ea4 100644
--- a/src/java/com/android/internal/telephony/RadioSimProxy.java
+++ b/src/java/com/android/internal/telephony/RadioSimProxy.java
@@ -21,6 +21,7 @@
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.Rlog;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.SimPhonebookRecord;
 
@@ -168,6 +169,7 @@
      * @throws RemoteException
      */
     public void getCdmaSubscription(int serial) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             mSimProxy.getCdmaSubscription(serial);
@@ -182,6 +184,7 @@
      * @throws RemoteException
      */
     public void getCdmaSubscriptionSource(int serial) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             mSimProxy.getCdmaSubscriptionSource(serial);
@@ -631,6 +634,7 @@
      * @throws RemoteException
      */
     public void setCdmaSubscriptionSource(int serial, int cdmaSub) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             mSimProxy.setCdmaSubscriptionSource(serial, cdmaSub);
diff --git a/src/java/com/android/internal/telephony/RadioVoiceProxy.java b/src/java/com/android/internal/telephony/RadioVoiceProxy.java
index e57a61d..d85017b 100644
--- a/src/java/com/android/internal/telephony/RadioVoiceProxy.java
+++ b/src/java/com/android/internal/telephony/RadioVoiceProxy.java
@@ -21,6 +21,8 @@
 import android.telephony.Rlog;
 import android.telephony.emergency.EmergencyNumber;
 
+import com.android.internal.telephony.flags.Flags;
+
 import java.util.ArrayList;
 
 /**
@@ -488,6 +490,7 @@
      * @throws RemoteException
      */
     public void sendCdmaFeatureCode(int serial, String featureCode) throws RemoteException {
+        if (Flags.cleanupCdma()) return;
         if (isEmpty()) return;
         if (isAidl()) {
             mVoiceProxy.sendCdmaFeatureCode(serial, featureCode);
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 2f2b62a..fac4358 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -20,6 +20,7 @@
 
 import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
 import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
+import static com.android.internal.telephony.SmsDispatchersController.PendingRequest;
 import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE;
 
 import android.annotation.UserIdInt;
@@ -824,6 +825,9 @@
 
         SmsResponse smsResponse = new SmsResponse(messageRef, null /* ackPdu */, NO_ERROR_CODE,
                 tracker.mMessageId);
+        if (Flags.temporaryFailuresInCarrierMessagingService()) {
+            tracker.mResultCodeFromCarrierMessagingService = result;
+        }
 
         switch (result) {
             case CarrierMessagingService.SEND_STATUS_OK:
@@ -835,10 +839,34 @@
                                                           smsResponse,
                                                           null /* exception*/)));
                 break;
-            case CarrierMessagingService.SEND_STATUS_ERROR:
-                Rlog.d(TAG, "processSendSmsResponse: Sending SMS by CarrierMessagingService"
-                        + " failed. "
-                        + SmsController.formatCrossStackMessageId(tracker.mMessageId));
+            case CarrierMessagingService.SEND_STATUS_ERROR: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_ERROR_GENERIC_FAILURE: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_ERROR_NULL_PDU: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_ERROR_NO_SERVICE: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_ERROR_LIMIT_EXCEEDED: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_ERROR_FDN_CHECK_FAILURE: // fall through
+            case CarrierMessagingService
+                    .SEND_STATUS_RESULT_ERROR_SHORT_CODE_NOT_ALLOWED: // fall through
+            case CarrierMessagingService
+                    .SEND_STATUS_RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_NETWORK_REJECT: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_INVALID_ARGUMENTS: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_INVALID_STATE: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_INVALID_SMS_FORMAT: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_NETWORK_ERROR: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_ENCODING_ERROR: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_INVALID_SMSC_ADDRESS: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_OPERATION_NOT_ALLOWED: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_CANCELLED: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_REQUEST_NOT_SUPPORTED: // fall through
+            case CarrierMessagingService
+                    .SEND_STATUS_RESULT_SMS_BLOCKED_DURING_EMERGENCY: // fall through
+            case CarrierMessagingService.SEND_STATUS_RESULT_SMS_SEND_RETRY_FAILED: // fall through
+                Rlog.d(
+                        TAG,
+                        "processSendSmsResponse: Sending SMS by CarrierMessagingService"
+                                + " failed. "
+                                + SmsController.formatCrossStackMessageId(tracker.mMessageId));
                 sendMessage(obtainMessage(EVENT_SEND_SMS_COMPLETE,
                         new AsyncResult(tracker, smsResponse,
                                 new CommandException(CommandException.Error.GENERIC_FAILURE))));
@@ -857,6 +885,55 @@
         }
     }
 
+    private int toSmsManagerResultForSendSms(int carrierMessagingServiceResult) {
+        switch (carrierMessagingServiceResult) {
+            case CarrierMessagingService.SEND_STATUS_OK:
+                return Activity.RESULT_OK;
+            case CarrierMessagingService.SEND_STATUS_ERROR:
+                return SmsManager.RESULT_RIL_GENERIC_ERROR;
+            case CarrierMessagingService.SEND_STATUS_RESULT_ERROR_GENERIC_FAILURE:
+                return SmsManager.RESULT_ERROR_GENERIC_FAILURE;
+            case CarrierMessagingService.SEND_STATUS_RESULT_ERROR_NULL_PDU:
+                return SmsManager.RESULT_ERROR_NULL_PDU;
+            case CarrierMessagingService.SEND_STATUS_RESULT_ERROR_NO_SERVICE:
+                return SmsManager.RESULT_ERROR_NO_SERVICE;
+            case CarrierMessagingService.SEND_STATUS_RESULT_ERROR_LIMIT_EXCEEDED:
+                return SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
+            case CarrierMessagingService.SEND_STATUS_RESULT_ERROR_FDN_CHECK_FAILURE:
+                return SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
+            case CarrierMessagingService.SEND_STATUS_RESULT_ERROR_SHORT_CODE_NOT_ALLOWED:
+                return SmsManager.RESULT_ERROR_SHORT_CODE_NOT_ALLOWED;
+            case CarrierMessagingService.SEND_STATUS_RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED:
+                return SmsManager.RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED;
+            case CarrierMessagingService.SEND_STATUS_RESULT_NETWORK_REJECT:
+                return SmsManager.RESULT_NETWORK_REJECT;
+            case CarrierMessagingService.SEND_STATUS_RESULT_INVALID_ARGUMENTS:
+                return SmsManager.RESULT_INVALID_ARGUMENTS;
+            case CarrierMessagingService.SEND_STATUS_RESULT_INVALID_STATE:
+                return SmsManager.RESULT_INVALID_STATE;
+            case CarrierMessagingService.SEND_STATUS_RESULT_INVALID_SMS_FORMAT:
+                return SmsManager.RESULT_INVALID_SMS_FORMAT;
+            case CarrierMessagingService.SEND_STATUS_RESULT_NETWORK_ERROR:
+                return SmsManager.RESULT_NETWORK_ERROR;
+            case CarrierMessagingService.SEND_STATUS_RESULT_ENCODING_ERROR:
+                return SmsManager.RESULT_ENCODING_ERROR;
+            case CarrierMessagingService.SEND_STATUS_RESULT_INVALID_SMSC_ADDRESS:
+                return SmsManager.RESULT_INVALID_SMSC_ADDRESS;
+            case CarrierMessagingService.SEND_STATUS_RESULT_OPERATION_NOT_ALLOWED:
+                return SmsManager.RESULT_OPERATION_NOT_ALLOWED;
+            case CarrierMessagingService.SEND_STATUS_RESULT_CANCELLED:
+                return SmsManager.RESULT_CANCELLED;
+            case CarrierMessagingService.SEND_STATUS_RESULT_REQUEST_NOT_SUPPORTED:
+                return SmsManager.RESULT_REQUEST_NOT_SUPPORTED;
+            case CarrierMessagingService.SEND_STATUS_RESULT_SMS_BLOCKED_DURING_EMERGENCY:
+                return SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY;
+            case CarrierMessagingService.SEND_STATUS_RESULT_SMS_SEND_RETRY_FAILED:
+                return SmsManager.RESULT_SMS_SEND_RETRY_FAILED;
+            default:
+                return SmsManager.RESULT_ERROR_GENERIC_FAILURE;
+        }
+    }
+
     /**
      * Use the carrier messaging service to send a multipart text SMS.
      */
@@ -1016,8 +1093,8 @@
      */
     protected void notifySmsSentFailedToEmergencyStateTracker(SmsTracker tracker,
             boolean isOverIms) {
-        mSmsDispatchersController.notifySmsSent(tracker.mDestAddress, tracker.mMessageId,
-                isOverIms, true /*isLastSmsPart*/, false /*success*/);
+        mSmsDispatchersController.notifySmsSent(tracker, isOverIms,
+            true /*isLastSmsPart*/, false /*success*/);
     }
 
     /**
@@ -1052,9 +1129,8 @@
             }
             tracker.onSent(mContext);
             mPhone.notifySmsSent(tracker.mDestAddress);
-            mSmsDispatchersController.notifySmsSent(
-                    tracker.mDestAddress, tracker.mMessageId, false,
-                    tracker.isSinglePartOrLastPart(), true /*success*/);
+            mSmsDispatchersController.notifySmsSent(tracker, false,
+                tracker.isSinglePartOrLastPart(), true /*success*/);
 
             mPhone.getSmsStats().onOutgoingSms(
                     tracker.mImsRetry > 0 /* isOverIms */,
@@ -1084,10 +1160,20 @@
                         + SmsController.formatCrossStackMessageId(tracker.mMessageId));
             }
 
-            int ss = mPhone.getServiceState().getState();
-            int error = rilErrorToSmsManagerResult(
-                    ((CommandException) (ar.exception)).getCommandError(), tracker);
+            int error;
+            if (Flags.temporaryFailuresInCarrierMessagingService()
+                    && tracker.mResultCodeFromCarrierMessagingService
+                            != CarrierMessagingService.SEND_STATUS_OK) {
+                error =
+                        toSmsManagerResultForSendSms(
+                                tracker.mResultCodeFromCarrierMessagingService);
+            } else {
+                error =
+                        rilErrorToSmsManagerResult(
+                                ((CommandException) (ar.exception)).getCommandError(), tracker);
+            }
 
+            int ss = mPhone.getServiceState().getState();
             if (tracker.mImsRetry > 0 && ss != ServiceState.STATE_IN_SERVICE) {
                 // This is retry after failure over IMS but voice is not available.
                 // Set retry to max allowed, so no retry is sent and cause
@@ -1399,7 +1485,7 @@
     @UnsupportedAppUsage
     protected void sendData(String callingPackage, int callingUser, String destAddr, String scAddr,
             int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
-            boolean isForVvm) {
+            boolean isForVvm, long uniqueMessageId) {
         int messageRef = nextMessageRef();
         SmsMessageBase.SubmitPduBase pdu = getSubmitPdu(
                 scAddr, destAddr, destPort, data, (deliveryIntent != null), messageRef);
@@ -1408,7 +1494,8 @@
             SmsTracker tracker = getSmsTracker(callingPackage, callingUser, map, sentIntent,
                     deliveryIntent, getFormat(), null /*messageUri*/, false /*expectMore*/,
                     null /*fullMessageText*/, false /*isText*/,
-                    true /*persistMessage*/, isForVvm, 0L /* messageId */, messageRef);
+                    true /*persistMessage*/, isForVvm, 0L /* messageId */, messageRef,
+                    uniqueMessageId);
 
             if (!sendSmsByCarrierApp(true /* isDataSms */, tracker)) {
                 sendSubmitPdu(tracker);
@@ -1526,7 +1613,7 @@
             long messageId) {
         sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, messageUri, callingPkg,
                 callingUser, persistMessage, priority, expectMore, validityPeriod, isForVvm,
-                messageId, false);
+                messageId, false, PendingRequest.getNextUniqueMessageId());
     }
 
     /**
@@ -1634,7 +1721,7 @@
             PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri,
             String callingPkg, int callingUser, boolean persistMessage, int priority,
             boolean expectMore, int validityPeriod, boolean isForVvm,
-            long messageId, boolean skipShortCodeCheck) {
+            long messageId, boolean skipShortCodeCheck, long uniqueMessageId) {
         Rlog.d(TAG, "sendText id: " + SmsController.formatCrossStackMessageId(messageId));
         int messageRef = nextMessageRef();
         SmsMessageBase.SubmitPduBase pdu = getSubmitPdu(
@@ -1645,7 +1732,7 @@
             SmsTracker tracker = getSmsTracker(callingPkg, callingUser, map, sentIntent,
                     deliveryIntent, getFormat(), messageUri, expectMore, text, true /*isText*/,
                     persistMessage, priority, validityPeriod, isForVvm, messageId, messageRef,
-                    skipShortCodeCheck);
+                    skipShortCodeCheck, uniqueMessageId);
 
             if (!sendSmsByCarrierApp(false /* isDataSms */, tracker)) {
                 sendSubmitPdu(tracker);
@@ -1827,7 +1914,7 @@
             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
             ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
             int callingUser, boolean persistMessage, int priority, boolean expectMore,
-            int validityPeriod, long messageId) {
+            int validityPeriod, long messageId, long uniqueMessageId) {
         final String fullMessageText = getMultipartMessageText(parts);
         int refNumber = getNextConcatenatedRef() & 0x00FF;
         int encoding = SmsConstants.ENCODING_UNKNOWN;
@@ -1890,7 +1977,7 @@
                         smsHeader, encoding, sentIntent, deliveryIntent, (i == (msgCount - 1)),
                         unsentPartCount, anyPartFailed, messageUri,
                         fullMessageText, priority, expectMore, validityPeriod, messageId,
-                        messageRef);
+                        messageRef, uniqueMessageId);
             if (trackers[i] == null) {
                 triggerSentIntentForFailure(sentIntents);
                 return;
@@ -1926,7 +2013,7 @@
             int encoding, PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart,
             AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
             String fullMessageText, int priority, boolean expectMore, int validityPeriod,
-            long messageId, int messageRef) {
+            long messageId, int messageRef, long uniqueMessageId) {
         if (isCdmaMo()) {
             UserData uData = new UserData();
             uData.payloadStr = message;
@@ -1956,7 +2043,7 @@
                         deliveryIntent, getFormat(), unsentPartCount, anyPartFailed, messageUri,
                         smsHeader, (!lastPart || expectMore), fullMessageText,  /*isText*/
                         true,  /*persistMessage*/ true, priority, validityPeriod,  /* isForVvm */
-                        false, messageId, messageRef, false);
+                        false, messageId, messageRef, false, uniqueMessageId);
             } else {
                 Rlog.e(TAG, "CdmaSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned "
                         + "null " + SmsController.formatCrossStackMessageId(messageId));
@@ -1975,7 +2062,7 @@
                         messageUri, smsHeader, (!lastPart || expectMore),
                         fullMessageText,  /*isText*/
                         true,  /*persistMessage*/ false, priority, validityPeriod,  /* isForVvm */
-                        false, messageId, messageRef, false);
+                        false, messageId, messageRef, false, uniqueMessageId);
             } else {
                 Rlog.e(TAG, "GsmSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned "
                         + "null " + SmsController.formatCrossStackMessageId(messageId));
@@ -2119,7 +2206,7 @@
             }
         }
 
-        if (mTelephonyManager.isEmergencyNumber(trackers[0].mDestAddress)) {
+        if (mPhone.hasCalling() && mTelephonyManager.isEmergencyNumber(trackers[0].mDestAddress)) {
             new AsyncEmergencyContactNotifier(mContext).execute();
         }
     }
@@ -2488,10 +2575,14 @@
 
         public final long mMessageId;
 
+        // A CarrierMessagingService result code to be returned to the caller.
+        public int mResultCodeFromCarrierMessagingService;
+
         private Boolean mIsFromDefaultSmsApplication;
 
         private int mCarrierId;
         private boolean mSkipShortCodeDestAddrCheck;
+        public final long mUniqueMessageId;
         // SMS anomaly uuid -- unexpected error from RIL
         private final UUID mAnomalyUnexpectedErrorFromRilUUID =
                 UUID.fromString("43043600-ea7a-44d2-9ae6-a58567ac7886");
@@ -2502,7 +2593,8 @@
                 SmsHeader smsHeader, boolean expectMore, String fullMessageText, int subId,
                 boolean isText, boolean persistMessage, int userId, int priority,
                 int validityPeriod, boolean isForVvm, long messageId, int carrierId,
-                int messageRef, boolean skipShortCodeDestAddrCheck) {
+                int messageRef, boolean skipShortCodeDestAddrCheck,
+                long uniqueMessageId) {
             mData = data;
             mSentIntent = sentIntent;
             mDeliveryIntent = deliveryIntent;
@@ -2529,6 +2621,29 @@
             mMessageId = messageId;
             mCarrierId = carrierId;
             mSkipShortCodeDestAddrCheck = skipShortCodeDestAddrCheck;
+            mUniqueMessageId = uniqueMessageId;
+            mResultCodeFromCarrierMessagingService = CarrierMessagingService.SEND_STATUS_OK;
+        }
+
+        @VisibleForTesting
+        public SmsTracker(String destAddr, long messageId, String messageText) {
+            mData = null;
+            mSentIntent = null;
+            mDeliveryIntent = null;
+            mAppInfo = null;
+            mDestAddress = destAddr;
+            mUsesImsServiceForIms = false;
+            mSmsHeader = null;
+            mMessageId = messageId;
+            mUserId = 0;
+            mPriority = 0;
+            mValidityPeriod = 0;
+            mIsForVvm = false;
+            mCarrierId = 0;
+            mSkipShortCodeDestAddrCheck = false;
+            mUniqueMessageId = 0;
+            mResultCodeFromCarrierMessagingService = CarrierMessagingService.SEND_STATUS_OK;
+            mFullMessageText = messageText;
         }
 
         public HashMap<String, Object> getData() {
@@ -2569,6 +2684,22 @@
         }
 
         /**
+         * Check if the message is a MT SMS polling message.
+         *
+         * @param context The Context
+         * @return true if the message is a MT SMS polling message, false otherwise.
+         */
+        public boolean isMtSmsPollingMessage(Context context) {
+            if (mFullMessageText == null) {
+                return false;
+            }
+
+            String mtSmsPollingText =
+                    context.getResources().getString(R.string.config_mt_sms_polling_text);
+            return mFullMessageText.equals(mtSmsPollingText);
+        }
+
+        /**
          * Update the status of this message if we persisted it
          */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -2624,6 +2755,14 @@
         }
 
         /**
+         * Returns the flag specifying whether any part of this {@link SmsTracker} failed to send
+         * or not.
+         */
+        protected boolean isAnyPartFailed() {
+            return mAnyPartFailed != null && mAnyPartFailed.get();
+        }
+
+        /**
          * Persist a sent SMS if required:
          * 1. It is a text message
          * 2. SmsApplication tells us to persist: sent from apps that are not default-SMS app or
@@ -2820,7 +2959,8 @@
             AtomicInteger unsentPartCount, AtomicBoolean anyPartFailed, Uri messageUri,
             SmsHeader smsHeader, boolean expectMore, String fullMessageText, boolean isText,
             boolean persistMessage, int priority, int validityPeriod, boolean isForVvm,
-            long messageId, int messageRef, boolean skipShortCodeCheck) {
+            long messageId, int messageRef, boolean skipShortCodeCheck,
+            long uniqueMessageId) {
         if (!Flags.smsMmsDeliverBroadcastsRedirectToMainUser()) {
             callingUser = UserHandle.getUserHandleForUid(Binder.getCallingUid()).getIdentifier();
         }
@@ -2841,31 +2981,33 @@
                 unsentPartCount, anyPartFailed, messageUri, smsHeader, expectMore,
                 fullMessageText, getSubId(), isText, persistMessage, callingUser, priority,
                 validityPeriod, isForVvm, messageId, mPhone.getCarrierId(), messageRef,
-                skipShortCodeCheck);
+                skipShortCodeCheck, uniqueMessageId);
     }
 
     protected SmsTracker getSmsTracker(String callingPackage, int callingUser,
             HashMap<String, Object> data, PendingIntent sentIntent, PendingIntent deliveryIntent,
             String format, Uri messageUri, boolean expectMore, String fullMessageText,
             boolean isText, boolean persistMessage, boolean isForVvm,
-            long messageId, int messageRef) {
+            long messageId, int messageRef, long uniqueMessageId) {
         return getSmsTracker(callingPackage, callingUser , data, sentIntent, deliveryIntent,
                 format, /*unsentPartCount*/ null, /*anyPartFailed*/ null, messageUri, /*smsHeader*/
                 null, expectMore, fullMessageText, isText,
                 persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
                 SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
-                isForVvm, messageId, messageRef, false);
+                isForVvm, messageId, messageRef, false, uniqueMessageId);
     }
 
     protected SmsTracker getSmsTracker(String callingPackage, int callingUser,
             HashMap<String, Object> data, PendingIntent sentIntent, PendingIntent deliveryIntent,
             String format, Uri messageUri, boolean expectMore, String fullMessageText,
             boolean isText, boolean persistMessage, int priority, int validityPeriod,
-            boolean isForVvm, long messageId, int messageRef, boolean skipShortCodeCheck) {
+            boolean isForVvm, long messageId, int messageRef, boolean skipShortCodeCheck,
+            long uniqueMessageId) {
         return getSmsTracker(callingPackage, callingUser, data, sentIntent, deliveryIntent,
                 format, /*unsentPartCount*/ null, /*anyPartFailed*/ null, messageUri, /*smsHeader*/
                 null, expectMore, fullMessageText, isText, persistMessage, priority,
-                validityPeriod, isForVvm, messageId, messageRef, skipShortCodeCheck);
+                validityPeriod, isForVvm, messageId, messageRef, skipShortCodeCheck,
+                uniqueMessageId);
     }
 
     protected HashMap<String, Object> getSmsTrackerMap(String destAddr, String scAddr,
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index e7500a2..c32db3d 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -76,6 +76,8 @@
 import android.telephony.TelephonyManager;
 import android.telephony.VoiceSpecificRegistrationInfo;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.telephony.satellite.ISatelliteModemStateCallback;
+import android.telephony.satellite.SatelliteManager;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.LocalLog;
@@ -327,18 +329,10 @@
     private boolean mImsRegistrationOnOff = false;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private boolean mDeviceShuttingDown = false;
+    private CarrierDisplayNameData mCarrierDisplayNameData =
+            new CarrierDisplayNameData.Builder().build();
     /** Keep track of SPN display rules, so we only broadcast intent if something changes. */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private String mCurSpn = null;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private String mCurDataSpn = null;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private String mCurPlmn = null;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private boolean mCurShowPlmn = false;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private boolean mCurShowSpn = false;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @VisibleForTesting
     public int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private int mPrevSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -452,7 +446,7 @@
                 // Once sub id becomes valid, we need to update the service provider name
                 // displayed on the UI again. The old SPN update intents sent to
                 // MobileSignalController earlier were actually ignored due to invalid sub id.
-                updateSpnDisplay();
+                updateCarrierDisplayName();
             }
             mSubId = curSubId;
         }
@@ -559,12 +553,12 @@
                 pollState();
                 // Depends on modem, ServiceState is not necessarily updated, so make sure updating
                 // SPN.
-                updateSpnDisplay();
+                updateCarrierDisplayName();
             } else if (action.equals(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED)) {
                 String lastKnownNetworkCountry = intent.getStringExtra(
                         TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY);
                 if (!mLastKnownNetworkCountry.equals(lastKnownNetworkCountry)) {
-                    updateSpnDisplay();
+                    updateCarrierDisplayName();
                 }
             }
         }
@@ -627,6 +621,83 @@
      */
     private AccessNetworksManagerCallback mAccessNetworksManagerCallback = null;
 
+    /**
+     * Listens status of nb iot satellite modem.
+     */
+    private SatelliteModemStateListener mSatelliteModemStateListener = null;
+
+    /**
+     * SatelliteModemStateListener class
+     */
+    protected class SatelliteModemStateListener extends ISatelliteModemStateCallback.Stub {
+
+        /**
+         * Satellite Modem Connection Status. True when satellite is connected
+         */
+        private boolean mSatelliteNbIotConnected = false;
+
+        /**
+         * Marks the satellite display change as true.
+         */
+        private boolean mUpdateSatelliteCarrierDisplay = false;
+
+        @Override
+        public void onSatelliteModemStateChanged(int state) {
+            boolean isConnected = isInConnectedState();
+            if (isConnected != mSatelliteNbIotConnected) {
+                log("Satellite connection state is changed to " + isConnected);
+                mSatelliteNbIotConnected = isConnected;
+                mUpdateSatelliteCarrierDisplay = true;
+                // trigger pollStats() because the service state is already OOO in demo mode.
+                pollState();
+            }
+        }
+
+        @Override
+        public void onEmergencyModeChanged(boolean isEmergency) {}
+
+        @Override
+        public void onRegistrationFailure(int causeCode) {}
+
+        @Override
+        public void onTerrestrialNetworkAvailableChanged(boolean isAvailable) {}
+
+        /**
+         * Returns true when statellite is connected.
+         *
+         * Note that this connection state is only applicable carrier roaming nb iot satellite.
+         */
+        public boolean isInConnectedState() {
+            SatelliteController sc = SatelliteController.getInstance();
+            if (sc == null) {
+                return false;
+            }
+            int subId = sc.getSelectedSatelliteSubId();
+            if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                    || subId != mPhone.getSubId()
+                    || subId == sc.getNtnOnlySubscriptionId()) {
+                return false;
+            }
+            return sc.isInConnectedState();
+        }
+
+        /**
+         * Returns true if the satellite connection has changed and the satellite display needs
+         * to be updated.
+         */
+        public boolean needToUpdateSatelliteCarrierDisplay() {
+            return mUpdateSatelliteCarrierDisplay;
+        }
+
+        /**
+         * The satellite carrier display update is complete and is marked to not be updated any
+         * further.
+         */
+        public void doneForUpdateSatelliteCarrierDisplay() {
+            mUpdateSatelliteCarrierDisplay = false;
+        }
+    }
+
     public ServiceStateTracker(GsmCdmaPhone phone, CommandsInterface ci,
             FeatureFlags featureFlags) {
         mNitzState = TelephonyComponentFactory.getInstance()
@@ -1245,7 +1316,7 @@
                 if (mUiccApplication == null
                         || mUiccApplication.getState() != AppState.APPSTATE_READY) {
                     mIsSimReady = false;
-                    updateSpnDisplay();
+                    updateCarrierDisplayName();
                 }
                 break;
 
@@ -1424,7 +1495,7 @@
                 updateOtaspState();
                 if (mPhone.isPhoneTypeGsm()) {
                     mCdnr.updateEfFromUsim((SIMRecords) mIccRecords);
-                    updateSpnDisplay();
+                    updateCarrierDisplayName();
                 }
                 break;
 
@@ -1531,7 +1602,7 @@
 
             case EVENT_IMS_CAPABILITY_CHANGED:
                 if (DBG) log("EVENT_IMS_CAPABILITY_CHANGED");
-                updateSpnDisplay();
+                updateCarrierDisplayName();
                 mImsCapabilityChangedRegistrants.notifyRegistrants();
                 break;
 
@@ -1623,7 +1694,7 @@
                     mCdnr.updateEfFromRuim((RuimRecords) mIccRecords);
                     updatePhoneObject();
                     if (mPhone.isPhoneTypeCdma()) {
-                        updateSpnDisplay();
+                        updateCarrierDisplayName();
                     } else {
                         RuimRecords ruim = (RuimRecords) mIccRecords;
                         if (ruim != null) {
@@ -2761,53 +2832,34 @@
         }
     }
 
-    private void notifySpnDisplayUpdate(CarrierDisplayNameData data) {
-        int subId = mPhone.getSubId();
-        // Update ACTION_SERVICE_PROVIDERS_UPDATED if any value changes
-        if (mSubId != subId
-                || data.shouldShowPlmn() != mCurShowPlmn
-                || data.shouldShowSpn() != mCurShowSpn
-                || !TextUtils.equals(data.getSpn(), mCurSpn)
-                || !TextUtils.equals(data.getDataSpn(), mCurDataSpn)
-                || !TextUtils.equals(data.getPlmn(), mCurPlmn)) {
+    private void notifyCarrierDisplayNameDataChanged() {
+        final String log = String.format("notifyCarrierDisplayNameDataChanged: "
+                        + "changed sending intent, "
+                        + "rule=%d, CarrierDisplayNameData=%s, subId=%d",
+                getCarrierNameDisplayBitmask(mSS),
+                mCarrierDisplayNameData,
+                mPhone.getSubId());
+        mCdnrLogs.log(log);
+        if (DBG) log(log);
 
-            final String log = String.format("updateSpnDisplay: changed sending intent, "
-                            + "rule=%d, showPlmn='%b', plmn='%s', showSpn='%b', spn='%s', "
-                            + "dataSpn='%s', subId='%d'",
-                    getCarrierNameDisplayBitmask(mSS),
-                    data.shouldShowPlmn(),
-                    data.getPlmn(),
-                    data.shouldShowSpn(),
-                    data.getSpn(),
-                    data.getDataSpn(),
-                    subId);
-            mCdnrLogs.log(log);
-            if (DBG) log("updateSpnDisplay: " + log);
 
-            Intent intent = new Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
-            intent.putExtra(TelephonyManager.EXTRA_SHOW_SPN, data.shouldShowSpn());
-            intent.putExtra(TelephonyManager.EXTRA_SPN, data.getSpn());
-            intent.putExtra(TelephonyManager.EXTRA_DATA_SPN, data.getDataSpn());
-            intent.putExtra(TelephonyManager.EXTRA_SHOW_PLMN, data.shouldShowPlmn());
-            intent.putExtra(TelephonyManager.EXTRA_PLMN, data.getPlmn());
-            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
-            mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-
-            if (SubscriptionManager.isValidSubscriptionId(subId)) {
-                mSubscriptionManagerService.setCarrierName(subId, TextUtils.emptyIfNull(
-                        getCarrierName(data.shouldShowPlmn(), data.getPlmn(),
-                                data.shouldShowSpn(), data.getSpn())));
-            }
-        }
-        mCurShowSpn = data.shouldShowSpn();
-        mCurShowPlmn = data.shouldShowPlmn();
-        mCurSpn = data.getSpn();
-        mCurDataSpn = data.getDataSpn();
-        mCurPlmn = data.getPlmn();
+        Intent intent = new Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
+        intent.putExtra(TelephonyManager.EXTRA_SHOW_SPN, mCarrierDisplayNameData.shouldShowSpn());
+        intent.putExtra(TelephonyManager.EXTRA_SPN, mCarrierDisplayNameData.getSpn());
+        intent.putExtra(TelephonyManager.EXTRA_DATA_SPN, mCarrierDisplayNameData.getDataSpn());
+        intent.putExtra(TelephonyManager.EXTRA_SHOW_PLMN, mCarrierDisplayNameData.shouldShowPlmn());
+        intent.putExtra(TelephonyManager.EXTRA_PLMN, mCarrierDisplayNameData.getPlmn());
+        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
+        mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
     @NonNull
-    private String getCarrierName(boolean showPlmn, String plmn, boolean showSpn, String spn) {
+    private String getCarrierName(CarrierDisplayNameData cdnd) {
+        boolean showPlmn = cdnd.shouldShowPlmn();
+        boolean showSpn = cdnd.shouldShowSpn();
+        String plmn = cdnd.getPlmn();
+        String spn = cdnd.getSpn();
+
         String carrierName = "";
         if (showPlmn) {
             carrierName = plmn;
@@ -2825,29 +2877,34 @@
         } else if (showSpn) {
             carrierName = spn;
         }
-        return carrierName;
+        return TextUtils.emptyIfNull(carrierName);
     }
 
-    private void updateSpnDisplayCdnr() {
-        log("updateSpnDisplayCdnr+");
-        CarrierDisplayNameData data = mCdnr.getCarrierDisplayNameData();
-        notifySpnDisplayUpdate(data);
-        log("updateSpnDisplayCdnr-");
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @VisibleForTesting
-    public void updateSpnDisplay() {
-        if (mCarrierConfig.getBoolean(
-                CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL)) {
-            updateSpnDisplayCdnr();
-        } else {
-            updateSpnDisplayLegacy();
+    public void updateCarrierDisplayName() {
+        final boolean useCdnr = mCarrierConfig.getBoolean(
+                CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL);
+
+        final CarrierDisplayNameData cdnd = useCdnr
+                ? mCdnr.getCarrierDisplayNameData()
+                : getCarrierDisplayNameLegacy();
+
+        final int subId = mPhone.getSubId();
+
+        // Avoid sending unnecessary updates
+        if (subId == mSubId && cdnd.equals(mCarrierDisplayNameData)) return;
+
+        if (SubscriptionManager.isValidSubscriptionId(subId)) {
+            mSubscriptionManagerService.setCarrierName(subId, getCarrierName(cdnd));
         }
+
+        mCarrierDisplayNameData = cdnd; // notify...() relies on the updated value
+        notifyCarrierDisplayNameDataChanged();
+
     }
 
-    private void updateSpnDisplayLegacy() {
-        log("updateSpnDisplayLegacy+");
+    private @NonNull CarrierDisplayNameData getCarrierDisplayNameLegacy() {
+        log("getCarrierDisplayNameLegacy+");
 
         String spn = null;
         String dataSpn = null;
@@ -2888,11 +2945,12 @@
                     .getStringArray(com.android.internal.R.array.wfcSpnFormats);
 
             if (voiceIdx < 0 || voiceIdx >= wfcSpnFormats.length) {
-                loge("updateSpnDisplay: KEY_WFC_SPN_FORMAT_IDX_INT out of bounds: " + voiceIdx);
+                loge("updateCarrierDisplayName: KEY_WFC_SPN_FORMAT_IDX_INT out of bounds: "
+                        + voiceIdx);
                 voiceIdx = 0;
             }
             if (dataIdx < 0 || dataIdx >= wfcSpnFormats.length) {
-                loge("updateSpnDisplay: KEY_WFC_DATA_SPN_FORMAT_IDX_INT out of bounds: "
+                loge("updateCarrierDisplayName: KEY_WFC_DATA_SPN_FORMAT_IDX_INT out of bounds: "
                         + dataIdx);
                 dataIdx = 0;
             }
@@ -2926,13 +2984,21 @@
                     .getStringArray(R.array.crossSimSpnFormats);
 
             if (crossSimSpnFormatIdx < 0 || crossSimSpnFormatIdx >= crossSimSpnFormats.length) {
-                loge("updateSpnDisplay: KEY_CROSS_SIM_SPN_FORMAT_INT out of bounds: "
+                loge("updateCarrierDisplayName: KEY_CROSS_SIM_SPN_FORMAT_INT out of bounds: "
                         + crossSimSpnFormatIdx);
                 crossSimSpnFormatIdx = 0;
             }
             crossSimSpnFormat = crossSimSpnFormats[crossSimSpnFormatIdx];
         }
 
+        String satellitePlmn = null;
+        SatelliteModemStateListener satelliteModemStateListener = getSatelliteModemStateListener();
+        if (satelliteModemStateListener != null
+                && satelliteModemStateListener.isInConnectedState()) {
+            satellitePlmn = getSatelliteDisplayName();
+        }
+        log("updateCarrierDisplayName: satellitePlmn=" + satellitePlmn);
+
         if (mPhone.isPhoneTypeGsm()) {
             // The values of plmn/showPlmn change in different scenarios.
             // 1) No service but emergency call allowed -> expected
@@ -2971,21 +3037,25 @@
                             .toString();
                     noService = true;
                 }
-                if (DBG) log("updateSpnDisplay: radio is on but out " +
-                        "of service, set plmn='" + plmn + "'");
+                if (DBG) {
+                    log("updateCarrierDisplayName: radio is on but out "
+                            + "of service, set plmn='" + plmn + "'");
+                }
             } else if (combinedRegState == ServiceState.STATE_IN_SERVICE) {
                 // In either home or roaming service
                 plmn = mSS.getOperatorAlpha();
                 showPlmn = !TextUtils.isEmpty(plmn) &&
                         ((rule & CARRIER_NAME_DISPLAY_BITMASK_SHOW_PLMN)
                                 == CARRIER_NAME_DISPLAY_BITMASK_SHOW_PLMN);
-                if (DBG) log("updateSpnDisplay: rawPlmn = " + plmn);
+                if (DBG) log("updateCarrierDisplayName: rawPlmn = " + plmn);
             } else {
                 // Power off state, such as airplane mode, show plmn as null
                 showPlmn = true;
                 plmn = null;
-                if (DBG) log("updateSpnDisplay: radio is off w/ showPlmn="
-                        + showPlmn + " plmn=" + plmn);
+                if (DBG) {
+                    log("updateCarrierDisplayName: radio is off w/ showPlmn="
+                            + showPlmn + " plmn=" + plmn);
+                }
             }
 
             // The value of spn/showSpn are same in different scenarios.
@@ -2997,8 +3067,13 @@
             showSpn = !noService && !TextUtils.isEmpty(spn)
                     && ((rule & CARRIER_NAME_DISPLAY_BITMASK_SHOW_SPN)
                     == CARRIER_NAME_DISPLAY_BITMASK_SHOW_SPN);
-            if (DBG) log("updateSpnDisplay: rawSpn = " + spn);
-            if (!TextUtils.isEmpty(crossSimSpnFormat)) {
+            if (DBG) log("updateCarrierDisplayName: rawSpn = " + spn);
+            if (!TextUtils.isEmpty(satellitePlmn)) {
+                plmn = satellitePlmn;
+                showPlmn = true;
+                showSpn = false;
+                log("updateCarrierDisplayName: Update satellite network name:" + plmn);
+            } else if (!TextUtils.isEmpty(crossSimSpnFormat)) {
                 if (!TextUtils.isEmpty(spn)) {
                     // Show SPN + Cross-SIM Calling If SIM has SPN and SPN display condition
                     // is satisfied or SPN override is enabled for this carrier.
@@ -3056,7 +3131,7 @@
 
             // mOperatorAlpha contains the ERI text
             plmn = mSS.getOperatorAlpha();
-            if (DBG) log("updateSpnDisplay: cdma rawPlmn = " + plmn);
+            if (DBG) log("updateCarrierDisplayName: cdma rawPlmn = " + plmn);
 
             showPlmn = plmn != null;
 
@@ -3068,8 +3143,8 @@
                 // todo: temporary hack; should have a better fix. This is to avoid using operator
                 // name from ServiceState (populated in processIwlanRegistrationInfo()) until
                 // wifi calling is actually enabled
-                log("updateSpnDisplay: overwriting plmn from " + plmn + " to null as radio " +
-                        "state is off");
+                log("updateCarrierDisplayName: overwriting plmn from "
+                        + plmn + " to null as radio " + "state is off");
                 plmn = null;
             }
 
@@ -3077,20 +3152,67 @@
                 plmn = Resources.getSystem().getText(com.android.internal.R.string
                         .lockscreen_carrier_default).toString();
                 if (DBG) {
-                    log("updateSpnDisplay: radio is on but out of svc, set plmn='" + plmn + "'");
+                    log("updateCarrierDisplayName: radio is on but out of svc, set plmn='"
+                            + plmn + "'");
                 }
             }
-
         }
 
-        notifySpnDisplayUpdate(new CarrierDisplayNameData.Builder()
+        log("getCarrierDisplayNameLegacy-");
+
+        return new CarrierDisplayNameData.Builder()
                 .setSpn(spn)
                 .setDataSpn(dataSpn)
                 .setShowSpn(showSpn)
                 .setPlmn(plmn)
                 .setShowPlmn(showPlmn)
-                .build());
-        log("updateSpnDisplayLegacy-");
+                .build();
+    }
+
+    private void updateSatelliteDisplayOverride() {
+        String satelliteDisplayName = getSatelliteDisplayName();
+        if (TextUtils.isEmpty(satelliteDisplayName)) {
+            // Return, if there is no value to override.
+            return;
+        }
+
+        SatelliteModemStateListener satelliteModemStateListener = getSatelliteModemStateListener();
+        String operator = mNewSS.getOperatorAlphaLong();
+        SatelliteController sc = SatelliteController.getInstance();
+        // Override satellite display name if device is in carrier roaming nb iot ntn mode
+        // and has a valid operator
+        if (satelliteModemStateListener != null
+                && satelliteModemStateListener.isInConnectedState()
+                || (!TextUtils.isEmpty(operator)
+                        && sc != null && sc.isInCarrierRoamingNbIotNtn())) {
+            // override satellite display name
+            mNewSS.setOperatorName(
+                    satelliteDisplayName, satelliteDisplayName, mNewSS.getOperatorNumeric());
+            log("Override satellite display name to " + satelliteDisplayName);
+        }
+    }
+
+    @Nullable
+    private SatelliteModemStateListener getSatelliteModemStateListener() {
+        if (mSatelliteModemStateListener != null) {
+            return mSatelliteModemStateListener;
+        }
+
+        SatelliteController sc = SatelliteController.getInstance();
+        if (sc != null) {
+            SatelliteModemStateListener listener = new SatelliteModemStateListener();
+            if (sc.registerForSatelliteModemStateChanged(listener)
+                    == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
+                mSatelliteModemStateListener = listener;
+                log("created SatelliteModemStateListener");
+            }
+        }
+        return mSatelliteModemStateListener;
+    }
+
+    private String getSatelliteDisplayName() {
+        return mCarrierConfig.getString(
+                        CarrierConfigManager.KEY_SATELLITE_DISPLAY_NAME_STRING);
     }
 
     /**
@@ -3299,7 +3421,7 @@
         mImsRegistrationOnOff = registered;
 
         // It's possible ServiceState changes did not trigger SPN display update; we update it here.
-        updateSpnDisplay();
+        updateCarrierDisplayName();
     }
 
     public void onImsCapabilityChanged() {
@@ -3447,6 +3569,19 @@
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
         setPhyCellInfoFromCellIdentity(mNewSS, networkRegState.getCellIdentity());
 
+        boolean hasSatelliteConnectionChanged = false;
+        SatelliteModemStateListener satelliteModemStateListener = getSatelliteModemStateListener();
+        if (satelliteModemStateListener != null) {
+            hasSatelliteConnectionChanged =
+                    satelliteModemStateListener.needToUpdateSatelliteCarrierDisplay();
+            if (hasSatelliteConnectionChanged) {
+                log("Poll ServiceState done : hasSatelliteConnectionChanged="
+                        + hasSatelliteConnectionChanged);
+                satelliteModemStateListener.doneForUpdateSatelliteCarrierDisplay();
+                updateSatelliteDisplayOverride();
+            }
+        }
+
         if (DBG) {
             log("Poll ServiceState done: oldSS=" + mSS);
             log("Poll ServiceState done: newSS=" + mNewSS);
@@ -3713,11 +3848,12 @@
         String eriText = mPhone.getCdmaEriText();
         boolean hasEriChanged = !TextUtils.equals(mEriText, eriText);
         mEriText = eriText;
-        // Trigger updateSpnDisplay when
+        // Trigger updateCarrierDisplayName when
         // 1. Service state is changed.
         // 2. phone type is Cdma or CdmaLte and ERI text has changed.
-        if (hasChanged || (!mPhone.isPhoneTypeGsm() && hasEriChanged)) {
-            updateSpnDisplay();
+        if (hasChanged || (!mPhone.isPhoneTypeGsm() && hasEriChanged)
+                || hasSatelliteConnectionChanged) {
+            updateCarrierDisplayName();
         }
 
         if (hasChanged) {
@@ -5279,11 +5415,7 @@
         pw.println(" mStartedGprsRegCheck=" + mStartedGprsRegCheck);
         pw.println(" mReportedGprsNoReg=" + mReportedGprsNoReg);
         pw.println(" mNotification=" + mNotification);
-        pw.println(" mCurSpn=" + mCurSpn);
-        pw.println(" mCurDataSpn=" + mCurDataSpn);
-        pw.println(" mCurShowSpn=" + mCurShowSpn);
-        pw.println(" mCurPlmn=" + mCurPlmn);
-        pw.println(" mCurShowPlmn=" + mCurShowPlmn);
+        pw.println(" mCarrierDisplayNameData=" + mCarrierDisplayNameData);
         pw.flush();
         pw.println(" mCurrentOtaspMode=" + mCurrentOtaspMode);
         pw.println(" mRoamingIndicator=" + mRoamingIndicator);
diff --git a/src/java/com/android/internal/telephony/SimResponse.java b/src/java/com/android/internal/telephony/SimResponse.java
index 97692a0..12c9a3c 100644
--- a/src/java/com/android/internal/telephony/SimResponse.java
+++ b/src/java/com/android/internal/telephony/SimResponse.java
@@ -123,19 +123,34 @@
         if (!carrierRestrictions.allowedCarriersPrioritized) {
             carrierRestrictionDefault = CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_ALLOWED;
         }
-
-        CarrierRestrictionRules ret = CarrierRestrictionRules.newBuilder().setAllowedCarriers(
-                RILUtils.convertHalCarrierList(
-                        carrierRestrictions.allowedCarriers)).setExcludedCarriers(
-                RILUtils.convertHalCarrierList(
-                        carrierRestrictions.excludedCarriers)).setDefaultCarrierRestriction(
-                carrierRestrictionDefault).setMultiSimPolicy(policy).setCarrierRestrictionStatus(
-                carrierRestrictions.status).setAllowedCarrierInfo(
-                RILUtils.convertAidlCarrierInfoList(
-                        carrierRestrictions.allowedCarrierInfoList)).setExcludedCarrierInfo(
-                RILUtils.convertAidlCarrierInfoList(
-                        carrierRestrictions.excludedCarrierInfoList)).setCarrierLockInfoFeature(
-                carrierLockInfoSupported).build();
+        CarrierRestrictionRules ret = null;
+        if (carrierLockInfoSupported) {
+            // In order to support the old API { @link TelephonyManager#getAllowedCarriers() } we
+            // are parsing the allowedCarrierInfoList to CarrierIdentifier List also along with
+            // CarrierInfo List
+            ret = CarrierRestrictionRules.newBuilder().setAllowedCarriers(
+                    RILUtils.convertAidlCarrierInfoListToHalCarrierList(
+                            carrierRestrictions.allowedCarrierInfoList)).setExcludedCarriers(
+                    RILUtils.convertAidlCarrierInfoListToHalCarrierList(
+                            carrierRestrictions.excludedCarrierInfoList)).
+                    setDefaultCarrierRestriction(
+                    carrierRestrictionDefault).setMultiSimPolicy(
+                    policy).setCarrierRestrictionStatus(
+                    carrierRestrictions.status).setAllowedCarrierInfo(
+                    RILUtils.convertAidlCarrierInfoList(
+                            carrierRestrictions.allowedCarrierInfoList)).setExcludedCarrierInfo(
+                    RILUtils.convertAidlCarrierInfoList(
+                            carrierRestrictions.excludedCarrierInfoList)).setCarrierLockInfoFeature(
+                            true).build();
+        } else {
+            ret = CarrierRestrictionRules.newBuilder().setAllowedCarriers(
+                    RILUtils.convertHalCarrierList(
+                            carrierRestrictions.allowedCarriers)).setExcludedCarriers(
+                    RILUtils.convertHalCarrierList(
+                            carrierRestrictions.excludedCarriers)).setDefaultCarrierRestriction(
+                    carrierRestrictionDefault).setMultiSimPolicy(
+                    policy).build();
+        }
         if (responseInfo.error == RadioError.NONE) {
             RadioResponse.sendMessageResponse(rr.mResult, ret);
         }
diff --git a/src/java/com/android/internal/telephony/SimultaneousCallingTracker.java b/src/java/com/android/internal/telephony/SimultaneousCallingTracker.java
index 0b427f8..12cc2fa 100644
--- a/src/java/com/android/internal/telephony/SimultaneousCallingTracker.java
+++ b/src/java/com/android/internal/telephony/SimultaneousCallingTracker.java
@@ -366,9 +366,8 @@
             SubscriptionInfo subInfo =
                     SubscriptionManagerService.getInstance().getSubscriptionInfo(subId);
 
-            if (mFeatureFlags.dataOnlyCellularService() &&
-                    subId > SubscriptionManager.INVALID_SUBSCRIPTION_ID && subInfo != null &&
-                    subInfo.getServiceCapabilities()
+            if (subId > SubscriptionManager.INVALID_SUBSCRIPTION_ID && subInfo != null
+                    && subInfo.getServiceCapabilities()
                             .contains(SubscriptionManager.SERVICE_CAPABILITY_VOICE)) {
                 Log.v(LOG_TAG, "generateVoiceCapablePhoneMapBasedOnUserAssociation: adding "
                         + "phoneId = " + phone.getPhoneId());
diff --git a/src/java/com/android/internal/telephony/SmsController.java b/src/java/com/android/internal/telephony/SmsController.java
index e3c409d..051fbbd 100644
--- a/src/java/com/android/internal/telephony/SmsController.java
+++ b/src/java/com/android/internal/telephony/SmsController.java
@@ -276,6 +276,7 @@
         }
         UserHandle callingUser = Binder.getCallingUserHandle();
 
+
         Rlog.d(LOG_TAG, "sendTextForSubscriber caller=" + callingPackage);
 
         if (skipFdnCheck || skipShortCodeCheck) {
@@ -1194,8 +1195,9 @@
         }
 
         // Skip FDN check for emergency numbers
+        if (!TelephonyCapabilities.supportsTelephonyCalling(mFlags, mContext)) return false;
         TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-        if (tm.isEmergencyNumber(destAddr)) {
+        if (tm != null && tm.isEmergencyNumber(destAddr)) {
             return false;
         }
 
diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java
index 7795955..de5cc53 100644
--- a/src/java/com/android/internal/telephony/SmsDispatchersController.java
+++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java
@@ -45,12 +45,14 @@
 import android.telephony.ServiceState;
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.satellite.SatelliteManager;
 import android.text.TextUtils;
 
 import com.android.ims.ImsManager;
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
@@ -75,6 +77,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  *
@@ -136,12 +139,12 @@
     /** Time at which the current PARTIAL_SEGMENT_WAIT_DURATION timer was started */
     private long mCurrentWaitStartTime = INVALID_TIME;
 
-    private SMSDispatcher mCdmaDispatcher;
+    private SMSDispatcher mCdmaDispatcher = null;
     private SMSDispatcher mGsmDispatcher;
     private ImsSmsDispatcher mImsSmsDispatcher;
 
     private GsmInboundSmsHandler mGsmInboundSmsHandler;
-    private CdmaInboundSmsHandler mCdmaInboundSmsHandler;
+    private CdmaInboundSmsHandler mCdmaInboundSmsHandler = null;
 
     private Phone mPhone;
     /** Outgoing message counter. Shared by all dispatchers. */
@@ -220,6 +223,7 @@
         public static final int TYPE_TEXT = 2;
         public static final int TYPE_MULTIPART_TEXT = 3;
         public static final int TYPE_RETRY_SMS = 4;
+        private static final AtomicLong sNextUniqueMessageId = new AtomicLong(0);
 
         public final int type;
         public final SMSDispatcher.SmsTracker tracker;
@@ -242,13 +246,16 @@
         public final int validityPeriod;
         public final long messageId;
         public final boolean skipShortCodeCheck;
+        public final long uniqueMessageId;
+        public final boolean isMtSmsPolling;
 
         public PendingRequest(int type, SMSDispatcher.SmsTracker tracker, String callingPackage,
                 int callingUser, String destAddr, String scAddr,
                 ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents,
                 boolean isForVvm, byte[] data, int destPort, ArrayList<String> texts,
                 Uri messageUri, boolean persistMessage, int priority, boolean expectMore,
-                int validityPeriod, long messageId, boolean skipShortCodeCheck) {
+                int validityPeriod, long messageId, boolean skipShortCodeCheck,
+                boolean isMtSmsPolling) {
             this.type = type;
             this.tracker = tracker;
             this.callingPackage = callingPackage;
@@ -270,6 +277,17 @@
             this.validityPeriod = validityPeriod;
             this.messageId = messageId;
             this.skipShortCodeCheck = skipShortCodeCheck;
+            if (tracker != null) {
+                this.uniqueMessageId = tracker.mUniqueMessageId;
+            } else {
+                this.uniqueMessageId = getNextUniqueMessageId();
+            }
+            this.isMtSmsPolling = isMtSmsPolling;
+        }
+
+        public static long getNextUniqueMessageId() {
+            return sNextUniqueMessageId.getAndUpdate(
+                id -> ((id + 1) % Long.MAX_VALUE));
         }
     }
 
@@ -391,11 +409,14 @@
         // Create dispatchers, inbound SMS handlers and
         // broadcast undelivered messages in raw table.
         mImsSmsDispatcher = new ImsSmsDispatcher(phone, this, ImsManager::getConnector);
-        mCdmaDispatcher = new CdmaSMSDispatcher(phone, this);
         mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(phone.getContext(),
                 storageMonitor, phone, looper, mFeatureFlags);
-        mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(phone.getContext(),
-                storageMonitor, phone, (CdmaSMSDispatcher) mCdmaDispatcher, looper, mFeatureFlags);
+        if (!mFeatureFlags.cleanupCdma()) {
+            mCdmaDispatcher = new CdmaSMSDispatcher(phone, this);
+            mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(phone.getContext(),
+                    storageMonitor, phone, (CdmaSMSDispatcher) mCdmaDispatcher, looper,
+                    mFeatureFlags);
+        }
         mGsmDispatcher = new GsmSMSDispatcher(phone, this, mGsmInboundSmsHandler);
         SmsBroadcastUndelivered.initialize(phone.getContext(),
                 mGsmInboundSmsHandler, mCdmaInboundSmsHandler, mFeatureFlags);
@@ -437,9 +458,9 @@
         mCi.unregisterForImsNetworkStateChanged(this);
         mPhone.unregisterForServiceStateChanged(this);
         mGsmDispatcher.dispose();
-        mCdmaDispatcher.dispose();
+        if (mCdmaDispatcher != null) mCdmaDispatcher.dispose();
         mGsmInboundSmsHandler.dispose();
-        mCdmaInboundSmsHandler.dispose();
+        if (mCdmaInboundSmsHandler != null) mCdmaInboundSmsHandler.dispose();
         // Cancels the domain selection request if it's still in progress.
         finishDomainSelection(mDscHolder);
         finishDomainSelection(mEmergencyDscHolder);
@@ -559,7 +580,7 @@
 
             default:
                 if (isCdmaMo()) {
-                    mCdmaDispatcher.handleMessage(msg);
+                    if (mCdmaDispatcher != null) mCdmaDispatcher.handleMessage(msg);
                 } else {
                     mGsmDispatcher.handleMessage(msg);
                 }
@@ -641,7 +662,8 @@
 
     private void handlePartialSegmentTimerExpiry(long waitTimerStart) {
         if (mGsmInboundSmsHandler.getCurrentState().getName().equals("WaitingState")
-                || mCdmaInboundSmsHandler.getCurrentState().getName().equals("WaitingState")) {
+                || (mCdmaInboundSmsHandler != null
+                && mCdmaInboundSmsHandler.getCurrentState().getName().equals("WaitingState"))) {
             logd("handlePartialSegmentTimerExpiry: ignoring timer expiry as InboundSmsHandler is"
                     + " in WaitingState");
             return;
@@ -772,7 +794,7 @@
                         + ", format=" + format + "to mGsmInboundSmsHandler");
                 mGsmInboundSmsHandler.sendMessage(
                         InboundSmsHandler.EVENT_INJECT_SMS, isOverIms ? 1 : 0, token, ar);
-            } else if (format.equals(SmsConstants.FORMAT_3GPP2)) {
+            } else if (format.equals(SmsConstants.FORMAT_3GPP2) && mCdmaInboundSmsHandler != null) {
                 Rlog.i(TAG, "SmsDispatchersController:injectSmsText Sending msg=" + msg
                         + ", format=" + format + "to mCdmaInboundSmsHandler");
                 mCdmaInboundSmsHandler.sendMessage(
@@ -811,8 +833,7 @@
 
         if (!tracker.mUsesImsServiceForIms) {
             if (isSmsDomainSelectionEnabled()) {
-                TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-                boolean isEmergency = tm.isEmergencyNumber(tracker.mDestAddress);
+                boolean isEmergency = isEmergencyNumber(tracker.mDestAddress);
                 // This may be invoked by another thread, so this operation is posted and
                 // handled through the execution flow of SmsDispatchersController.
                 SomeArgs args = SomeArgs.obtain();
@@ -821,7 +842,7 @@
                         null, UserHandle.USER_NULL, null, null,
                         null, null, false, null, 0,
                         null, null, false,
-                        0, false, 0, 0L, false);
+                        0, false, 0, 0L, false, false);
                 args.arg3 = "sendRetrySms";
                 sendMessage(obtainMessage(EVENT_REQUEST_DOMAIN_SELECTION, args));
                 return;
@@ -867,8 +888,8 @@
                 // should never come here...
                 Rlog.e(TAG, "sendRetrySms failed to re-encode per missing fields!");
                 tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE);
-                notifySmsSent(tracker.mDestAddress, tracker.mMessageId,
-                        !retryUsingImsService, true /*isLastSmsPart*/, false /*success*/);
+                notifySmsSent(tracker, !retryUsingImsService,
+                        true /*isLastSmsPart*/, false /*success*/);
                 return;
             }
             String scAddr = (String) map.get("scAddr");
@@ -876,8 +897,8 @@
             if (destAddr == null) {
                 Rlog.e(TAG, "sendRetrySms failed due to null destAddr");
                 tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE);
-                notifySmsSent(tracker.mDestAddress, tracker.mMessageId,
-                        !retryUsingImsService, true /*isLastSmsPart*/, false /*success*/);
+                notifySmsSent(tracker, !retryUsingImsService,
+                        true /*isLastSmsPart*/, false /*success*/);
                 return;
             }
 
@@ -918,8 +939,8 @@
                         + "scAddr: %s, "
                         + "destPort: %s", scAddr, map.get("destPort")));
                 tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE);
-                notifySmsSent(tracker.mDestAddress, tracker.mMessageId,
-                        !retryUsingImsService, true /*isLastSmsPart*/, false /*success*/);
+                notifySmsSent(tracker, !retryUsingImsService,
+                    true /*isLastSmsPart*/, false /*success*/);
                 return;
             }
             // replace old smsc and pdu with newly encoded ones
@@ -981,6 +1002,7 @@
      * @return true if Cdma format should be used for MO SMS, false otherwise.
      */
     protected boolean isCdmaMo() {
+        if (mFeatureFlags.cleanupCdma()) return false;
         if (!isIms()) {
             // IMS is not registered, use Voice technology to determine SMS format.
             return (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType());
@@ -996,6 +1018,7 @@
      * @return true if format given is CDMA format, false otherwise.
      */
     public boolean isCdmaFormat(String format) {
+        if (mFeatureFlags.cleanupCdma()) return false;
         return (mCdmaDispatcher.getFormat().equals(format));
     }
 
@@ -1201,8 +1224,7 @@
     private void handleSmsSentCompletedUsingDomainSelection(@NonNull String destAddr,
             long messageId, boolean success, boolean isOverIms, boolean isLastSmsPart) {
         if (mEmergencyStateTracker != null) {
-            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-            if (tm.isEmergencyNumber(destAddr)) {
+            if (isEmergencyNumber(destAddr)) {
                 mEmergencyStateTracker.endSms(String.valueOf(messageId), success,
                         isOverIms ? NetworkRegistrationInfo.DOMAIN_PS
                                   : NetworkRegistrationInfo.DOMAIN_CS,
@@ -1214,11 +1236,12 @@
     /**
      * Called when MO SMS is sent.
      */
-    protected void notifySmsSent(@NonNull String destAddr, long messageId, boolean isOverIms,
-            boolean isLastSmsPart, boolean success) {
-        notifySmsSentToEmergencyStateTracker(
-                destAddr, messageId, isOverIms, isLastSmsPart, success);
-        notifySmsSentToDatagramDispatcher(messageId, success);
+    protected void notifySmsSent(@NonNull SMSDispatcher.SmsTracker tracker,
+            boolean isOverIms, boolean isLastSmsPart, boolean success) {
+        notifySmsSentToEmergencyStateTracker(tracker.mDestAddress,
+            tracker.mMessageId, isOverIms, isLastSmsPart, success);
+        notifySmsSentToDatagramDispatcher(tracker.mUniqueMessageId,
+                tracker.isSinglePartOrLastPart(), success && !tracker.isAnyPartFailed());
     }
 
     /**
@@ -1238,9 +1261,12 @@
         }
     }
 
-    private void notifySmsSentToDatagramDispatcher(long messageId, boolean success) {
-        if (SatelliteController.getInstance().isInCarrierRoamingNbIotNtn()) {
-            DatagramDispatcher.getInstance().onSendSmsDone(mPhone.getSubId(), messageId, success);
+    private void notifySmsSentToDatagramDispatcher(
+            long messageId, boolean isLastSmsPart, boolean success) {
+        if (SatelliteController.getInstance().shouldSendSmsToDatagramDispatcher(mPhone)
+                && isLastSmsPart) {
+            DatagramDispatcher.getInstance().onSendSmsDone(
+                    mPhone.getSubId(), messageId, success);
         }
     }
 
@@ -1251,8 +1277,7 @@
      */
     private void handleSmsReceivedViaIms(@Nullable String origAddr) {
         if (mEmergencyStateTracker != null) {
-            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-            if (origAddr != null && tm.isEmergencyNumber(origAddr)) {
+            if (origAddr != null && isEmergencyNumber(origAddr)) {
                 mEmergencyStateTracker.onEmergencySmsReceived();
             }
         }
@@ -1270,7 +1295,9 @@
 
     private boolean isTestEmergencyNumber(String number) {
         try {
+            if (!mPhone.hasCalling()) return false;
             TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            if (tm == null) return false;
             Map<Integer, List<EmergencyNumber>> eMap = tm.getEmergencyNumberList();
             return eMap.values().stream().flatMap(Collection::stream).anyMatch(eNumber ->
                     eNumber.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST)
@@ -1402,15 +1429,18 @@
         if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
             mImsSmsDispatcher.sendData(request.callingPackage, request.callingUser,
                     request.destAddr, request.scAddr, request.destPort, request.data,
-                    request.sentIntents.get(0), request.deliveryIntents.get(0), request.isForVvm);
+                    request.sentIntents.get(0), request.deliveryIntents.get(0), request.isForVvm,
+                    request.uniqueMessageId);
         } else if (isCdmaMo(domain)) {
             mCdmaDispatcher.sendData(request.callingPackage, request.callingUser, request.destAddr,
                     request.scAddr, request.destPort, request.data,
-                    request.sentIntents.get(0), request.deliveryIntents.get(0), request.isForVvm);
+                    request.sentIntents.get(0), request.deliveryIntents.get(0), request.isForVvm,
+                    request.uniqueMessageId);
         } else {
             mGsmDispatcher.sendData(request.callingPackage, request.callingUser, request.destAddr,
                     request.scAddr, request.destPort, request.data,
-                    request.sentIntents.get(0), request.deliveryIntents.get(0), request.isForVvm);
+                    request.sentIntents.get(0), request.deliveryIntents.get(0), request.isForVvm,
+                    request.uniqueMessageId);
         }
     }
 
@@ -1430,7 +1460,7 @@
                     request.messageUri, request.callingPackage, request.callingUser,
                     request.persistMessage, request.priority,  /*request.expectMore*/ false,
                     request.validityPeriod, request.isForVvm, request.messageId,
-                    request.skipShortCodeCheck);
+                    request.skipShortCodeCheck, request.uniqueMessageId);
         } else {
             if (isCdmaMo(domain)) {
                 mCdmaDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0),
@@ -1438,14 +1468,14 @@
                         request.messageUri, request.callingPackage, request.callingUser,
                         request.persistMessage, request.priority, request.expectMore,
                         request.validityPeriod, request.isForVvm, request.messageId,
-                        request.skipShortCodeCheck);
+                        request.skipShortCodeCheck, request.uniqueMessageId);
             } else {
                 mGsmDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0),
                         request.sentIntents.get(0), request.deliveryIntents.get(0),
                         request.messageUri, request.callingPackage, request.callingUser,
                         request.persistMessage, request.priority, request.expectMore,
                         request.validityPeriod, request.isForVvm, request.messageId,
-                        request.skipShortCodeCheck);
+                        request.skipShortCodeCheck, request.uniqueMessageId);
             }
         }
     }
@@ -1465,25 +1495,29 @@
                     request.sentIntents, request.deliveryIntents, request.messageUri,
                     request.callingPackage, request.callingUser, request.persistMessage,
                     request.priority, false /*request.expectMore*/, request.validityPeriod,
-                    request.messageId);
+                    request.messageId, request.uniqueMessageId);
         } else {
             if (isCdmaMo(domain)) {
                 mCdmaDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts,
                         request.sentIntents, request.deliveryIntents, request.messageUri,
                         request.callingPackage, request.callingUser, request.persistMessage,
                         request.priority, request.expectMore, request.validityPeriod,
-                        request.messageId);
+                        request.messageId, request.uniqueMessageId);
             } else {
                 mGsmDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts,
                         request.sentIntents, request.deliveryIntents, request.messageUri,
                         request.callingPackage, request.callingUser, request.persistMessage,
                         request.priority, request.expectMore, request.validityPeriod,
-                        request.messageId);
+                        request.messageId, request.uniqueMessageId);
             }
         }
     }
 
-    private void triggerSentIntentForFailure(@NonNull PendingIntent sentIntent) {
+    private void triggerSentIntentForFailure(PendingIntent sentIntent) {
+        if (sentIntent == null) {
+            logd("sentIntent is null");
+            return;
+        }
         try {
             sentIntent.send(SmsManager.RESULT_ERROR_GENERIC_FAILURE);
         } catch (CanceledException e) {
@@ -1491,7 +1525,11 @@
         }
     }
 
-    private void triggerSentIntentForFailure(@NonNull List<PendingIntent> sentIntents) {
+    private void triggerSentIntentForFailure(List<PendingIntent> sentIntents) {
+        if (sentIntents == null) {
+            logd("sentIntents is null");
+            return;
+        }
         for (PendingIntent sentIntent : sentIntents) {
             triggerSentIntentForFailure(sentIntent);
         }
@@ -1598,20 +1636,23 @@
                             destAddr, scAddr, asArrayList(sentIntent),
                             asArrayList(deliveryIntent), isForVvm, data, destPort, null,
                             null, false, 0, false, 0,
-                            0L, false),
+                            0L, false, false),
                     "sendData");
             return;
         }
 
         if (mImsSmsDispatcher.isAvailable()) {
             mImsSmsDispatcher.sendData(callingPackage, callingUser, destAddr, scAddr, destPort,
-                    data, sentIntent, deliveryIntent, isForVvm);
+                    data, sentIntent, deliveryIntent, isForVvm,
+                    PendingRequest.getNextUniqueMessageId());
         } else if (isCdmaMo()) {
             mCdmaDispatcher.sendData(callingPackage, callingUser, destAddr, scAddr, destPort, data,
-                    sentIntent, deliveryIntent, isForVvm);
+                    sentIntent, deliveryIntent, isForVvm,
+                    PendingRequest.getNextUniqueMessageId());
         } else {
             mGsmDispatcher.sendData(callingPackage, callingUser, destAddr, scAddr, destPort, data,
-                    sentIntent, deliveryIntent, isForVvm);
+                    sentIntent, deliveryIntent, isForVvm,
+                    PendingRequest.getNextUniqueMessageId());
         }
     }
 
@@ -1831,22 +1872,27 @@
                 callingPkg, callingUser, destAddr, scAddr, asArrayList(sentIntent),
                 asArrayList(deliveryIntent), isForVvm, null, 0, asArrayList(text),
                 messageUri, persistMessage, priority, expectMore, validityPeriod, messageId,
-                skipShortCodeCheck);
+                skipShortCodeCheck, false);
 
-        if (SatelliteController.getInstance().isInCarrierRoamingNbIotNtn()) {
+        if (SatelliteController.getInstance().shouldSendSmsToDatagramDispatcher(mPhone)) {
             // Send P2P SMS using carrier roaming NB IOT NTN
             DatagramDispatcher.getInstance().sendSms(pendingRequest);
             return;
+        } else if (SatelliteController.getInstance().isInCarrierRoamingNbIotNtn()) {
+            Rlog.d(TAG, "Block SMS in carrier roaming NB IOT NTN mode.");
+            // Block SMS in satellite mode if P2P SMS is not supported.
+            triggerSentIntentForFailure(pendingRequest.sentIntents);
+            return;
         }
 
         sendTextInternal(pendingRequest);
     }
 
     private void sendTextInternal(PendingRequest request) {
-        logd("sendTextInternal: messageId=" + request.messageId);
+        logd("sendTextInternal: messageId=" + request.messageId
+                 + ", uniqueMessageId=" + request.uniqueMessageId);
         if (isSmsDomainSelectionEnabled()) {
-            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-            boolean isEmergency = tm.isEmergencyNumber(request.destAddr);
+            boolean isEmergency = isEmergencyNumber(request.destAddr);
             sendSmsUsingDomainSelection(getDomainSelectionConnectionHolder(isEmergency),
                     request, "sendText");
             return;
@@ -1859,7 +1905,7 @@
                     request.messageUri, request.callingPackage, request.callingUser,
                     request.persistMessage, request.priority, false /*expectMore*/,
                     request.validityPeriod, request.isForVvm, request.messageId,
-                    request.skipShortCodeCheck);
+                    request.skipShortCodeCheck, request.uniqueMessageId);
         } else {
             if (isCdmaMo()) {
                 mCdmaDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0),
@@ -1867,14 +1913,14 @@
                         request.messageUri, request.callingPackage, request.callingUser,
                         request.persistMessage, request.priority, request.expectMore,
                         request.validityPeriod, request.isForVvm, request.messageId,
-                        request.skipShortCodeCheck);
+                        request.skipShortCodeCheck, request.uniqueMessageId);
             } else {
                 mGsmDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0),
                         request.sentIntents.get(0), request.deliveryIntents.get(0),
                         request.messageUri, request.callingPackage, request.callingUser,
                         request.persistMessage, request.priority, request.expectMore,
                         request.validityPeriod, request.isForVvm, request.messageId,
-                        request.skipShortCodeCheck);
+                        request.skipShortCodeCheck, request.uniqueMessageId);
             }
         }
     }
@@ -1995,22 +2041,33 @@
         PendingRequest pendingRequest = new PendingRequest(PendingRequest.TYPE_MULTIPART_TEXT, null,
                 callingPkg, callingUser, destAddr, scAddr, sentIntents, deliveryIntents, false,
                 null, 0, parts, messageUri, persistMessage, priority, expectMore,
-                validityPeriod, messageId, false);
+                validityPeriod, messageId, false, false);
 
-        if (SatelliteController.getInstance().isInCarrierRoamingNbIotNtn()) {
+        if (SatelliteController.getInstance().shouldSendSmsToDatagramDispatcher(mPhone)) {
             // Send multipart P2P SMS using carrier roaming NB IOT NTN
             DatagramDispatcher.getInstance().sendSms(pendingRequest);
             return;
+        } else if (SatelliteController.getInstance().isInCarrierRoamingNbIotNtn()) {
+            Rlog.d(TAG, "Block SMS in carrier roaming NB IOT NTN mode.");
+            // Block SMS in satellite mode if P2P SMS is not supported.
+            triggerSentIntentForFailure(pendingRequest.sentIntents);
+            return;
         }
 
         sendMultipartTextInternal(pendingRequest);
     }
 
+    private boolean isEmergencyNumber(String number) {
+        if (!mPhone.hasCalling()) return false;
+        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+        if (tm == null) return false;
+        return tm.isEmergencyNumber(number);
+    }
+
     private void sendMultipartTextInternal(PendingRequest request) {
         logd("sendMultipartTextInternal: messageId=" + request.messageId);
         if (isSmsDomainSelectionEnabled()) {
-            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-            boolean isEmergency = tm.isEmergencyNumber(request.destAddr);
+            boolean isEmergency = isEmergencyNumber(request.destAddr);
             sendSmsUsingDomainSelection(getDomainSelectionConnectionHolder(isEmergency),
                     request, "sendMultipartText");
             return;
@@ -2021,20 +2078,20 @@
                     request.sentIntents, request.deliveryIntents, request.messageUri,
                     request.callingPackage, request.callingUser, request.persistMessage,
                     request.priority, false /*expectMore*/, request.validityPeriod,
-                    request.messageId);
+                    request.messageId, request.uniqueMessageId);
         } else {
             if (isCdmaMo()) {
                 mCdmaDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts,
                         request.sentIntents, request.deliveryIntents, request.messageUri,
                         request.callingPackage, request.callingUser, request.persistMessage,
                         request.priority, request.expectMore, request.validityPeriod,
-                        request.messageId);
+                        request.messageId, request.uniqueMessageId);
             } else {
                 mGsmDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts,
                         request.sentIntents, request.deliveryIntents, request.messageUri,
                         request.callingPackage, request.callingUser, request.persistMessage,
                         request.priority, request.expectMore, request.validityPeriod,
-                        request.messageId);
+                        request.messageId, request.uniqueMessageId);
             }
         }
     }
@@ -2172,7 +2229,7 @@
      */
     public void sendCarrierRoamingNbIotNtnText(@NonNull PendingRequest request) {
         if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
-            logd("onSendCarrierRoamingNbIotNtnTextError: carrier roaming nb iot ntn "
+            logd("sendCarrierRoamingNbIotNtnText: carrier roaming nb iot ntn "
                     + "feature flag is disabled");
             return;
         }
@@ -2200,6 +2257,44 @@
         sendMessage(obtainMessage(EVENT_SEND_TEXT_OVER_NTN_ERROR, pendingRequest));
     }
 
+    /**
+     * This API should be used only by {@link DatagramDispatcher} to send MT SMS Polling message
+     * over non-terrestrial network.
+     * To enable users to receive incoming messages, the device needs to send an MO SMS to itself
+     * to trigger SMSC to send all pending SMS to the particular subscription.
+     */
+    public void sendMtSmsPollingMessage() {
+        if (!SatelliteController.getInstance().shouldSendSmsToDatagramDispatcher(mPhone)) {
+            logd("sendMtSmsPollingMessage: not in roaming nb iot ntn");
+            return;
+        }
+
+        SubscriptionManager subscriptionManager = (SubscriptionManager) mContext
+                .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        String destAddr = subscriptionManager.getPhoneNumber(mPhone.getSubId());
+        if (TextUtils.isEmpty(destAddr)) {
+            logd("sendMtSmsPollingMessage: destAddr is null or empty.");
+            return;
+        }
+
+        String mtSmsPollingText = mContext.getResources()
+                .getString(R.string.config_mt_sms_polling_text);
+        if (TextUtils.isEmpty(mtSmsPollingText)) {
+            logd("sendMtSmsPollingMessage: mtSmsPollingText is null or empty.");
+            return;
+        }
+
+        String callingPackage = mContext.getPackageName();
+        PendingRequest pendingRequest = new PendingRequest(PendingRequest.TYPE_TEXT, null,
+                callingPackage, Binder.getCallingUserHandle().getIdentifier(), destAddr,
+                getSmscAddressFromUSIMWithPhoneIdentity(callingPackage), asArrayList(null),
+                asArrayList(null), false, null, 0, asArrayList(mtSmsPollingText), null, false, 0,
+                false, 5, 0L, true, true);
+
+        if (SatelliteController.getInstance().shouldSendSmsToDatagramDispatcher(mPhone)) {
+            DatagramDispatcher.getInstance().sendSms(pendingRequest);
+        }
+    }
 
     public interface SmsInjectionCallback {
         void onSmsInjectedResult(int result);
@@ -2207,9 +2302,9 @@
 
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mGsmInboundSmsHandler.dump(fd, pw, args);
-        mCdmaInboundSmsHandler.dump(fd, pw, args);
+        if (mCdmaInboundSmsHandler != null) mCdmaInboundSmsHandler.dump(fd, pw, args);
         mGsmDispatcher.dump(fd, pw, args);
-        mCdmaDispatcher.dump(fd, pw, args);
+        if (mCdmaDispatcher != null) mCdmaDispatcher.dump(fd, pw, args);
         mImsSmsDispatcher.dump(fd, pw, args);
     }
 
diff --git a/src/java/com/android/internal/telephony/SmsUsageMonitor.java b/src/java/com/android/internal/telephony/SmsUsageMonitor.java
index 8e4ac60..7fb5811 100644
--- a/src/java/com/android/internal/telephony/SmsUsageMonitor.java
+++ b/src/java/com/android/internal/telephony/SmsUsageMonitor.java
@@ -35,6 +35,7 @@
 import android.util.AtomicFile;
 import android.util.Xml;
 
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.util.XmlUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.telephony.Rlog;
@@ -113,6 +114,8 @@
     /** Context for retrieving regexes from XML resource. */
     private final Context mContext;
 
+    private final FeatureFlags mFeatureFlags;
+
     /** Country code for the cached short code pattern matcher. */
     private String mCurrentCountry;
 
@@ -255,8 +258,9 @@
      * @param context the context to use to load resources and get TelephonyManager service
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public SmsUsageMonitor(Context context) {
+    public SmsUsageMonitor(Context context, FeatureFlags flags) {
         mContext = context;
+        mFeatureFlags = flags;
         ContentResolver resolver = context.getContentResolver();
         mRoleManager = (RoleManager) mContext.getSystemService(Context.ROLE_SERVICE);
 
@@ -404,7 +408,8 @@
         synchronized (mSettingsObserverHandler) {
             TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
             // always allow emergency numbers
-            if (tm.isEmergencyNumber(destAddress)) {
+            if (TelephonyCapabilities.supportsTelephonyCalling(mFeatureFlags, mContext)
+                    && tm != null && tm.isEmergencyNumber(destAddress)) {
                 if (DBG) Rlog.d(TAG, "isEmergencyNumber");
                 return SmsManager.SMS_CATEGORY_NOT_SHORT_CODE;
             }
diff --git a/src/java/com/android/internal/telephony/TelephonyCapabilities.java b/src/java/com/android/internal/telephony/TelephonyCapabilities.java
index 71d3b14..b650b43 100644
--- a/src/java/com/android/internal/telephony/TelephonyCapabilities.java
+++ b/src/java/com/android/internal/telephony/TelephonyCapabilities.java
@@ -18,6 +18,8 @@
 
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.SystemProperties;
 
@@ -209,4 +211,14 @@
 
         return featureFlags.minimalTelephonyCdmCheck();
     }
+
+    /**
+     * @return true if this device supports telephony calling, false if it does not.
+     */
+    public static boolean supportsTelephonyCalling(@NonNull FeatureFlags featureFlags,
+            Context context) {
+        if (!TelephonyCapabilities.minimalTelephonyCdmCheck(featureFlags)) return true;
+        return context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CALLING);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index 10e97b6..b4a3ee6 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -301,8 +301,8 @@
         return new SmsStorageMonitor(phone, flags);
     }
 
-    public SmsUsageMonitor makeSmsUsageMonitor(Context context) {
-        return new SmsUsageMonitor(context);
+    public SmsUsageMonitor makeSmsUsageMonitor(Context context, FeatureFlags flags) {
+        return new SmsUsageMonitor(context, flags);
     }
 
     public ServiceStateTracker makeServiceStateTracker(GsmCdmaPhone phone, CommandsInterface ci,
diff --git a/src/java/com/android/internal/telephony/TelephonyCountryDetector.java b/src/java/com/android/internal/telephony/TelephonyCountryDetector.java
index c2d6018..1e07bc3 100644
--- a/src/java/com/android/internal/telephony/TelephonyCountryDetector.java
+++ b/src/java/com/android/internal/telephony/TelephonyCountryDetector.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony;
 
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -111,6 +110,7 @@
     @GuardedBy("mLock")
     private boolean mIsCountryCodesOverridden = false;
     private final RegistrantList mCountryCodeChangedRegistrants = new RegistrantList();
+    private boolean mIsWifiNetworkConnected = false;
 
     private FeatureFlags mFeatureFlags = null;
 
@@ -298,7 +298,7 @@
                 handleNetworkCountryCodeChangedEvent((NetworkCountryCodeInfo) msg.obj);
                 break;
             case EVENT_WIFI_CONNECTIVITY_STATE_CHANGED:
-                handleEventWifiConnectivityStateChanged();
+                handleEventWifiConnectivityStateChanged((boolean) msg.obj);
                 break;
             case EVENT_LOCATION_UPDATE_REQUEST_QUOTA_RESET:
                 evaluateRequestingLocationUpdates();
@@ -490,11 +490,21 @@
         }
     }
 
-    private void handleEventWifiConnectivityStateChanged() {
-        mWifiConnectivityStateChangedRegistrantList.notifyResult(isWifiNetworkConnected());
+    private void handleEventWifiConnectivityStateChanged(boolean connected) {
+        logd("handleEventWifiConnectivityStateChanged: " + connected);
+        evaluateNotifyWifiConnectivityStateChangedEvent(connected);
         evaluateRequestingLocationUpdates();
     }
 
+    private void evaluateNotifyWifiConnectivityStateChangedEvent(boolean connected) {
+        if (connected != mIsWifiNetworkConnected) {
+            mIsWifiNetworkConnected = connected;
+            mWifiConnectivityStateChangedRegistrantList.notifyResult(mIsWifiNetworkConnected);
+            logd("evaluateNotifyWifiConnectivityStateChangedEvent: wifi connectivity state has "
+                    + "changed to " + connected);
+        }
+    }
+
     private void setLocationCountryCode(@NonNull Pair<String, Long> countryCodeInfo) {
         logd("Set location country code to: " + countryCodeInfo.first);
         if (!isValid(countryCodeInfo.first)) {
@@ -526,25 +536,23 @@
     private void registerForWifiConnectivityStateChanged() {
         logd("registerForWifiConnectivityStateChanged");
         NetworkRequest.Builder builder = new NetworkRequest.Builder();
-        builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+        builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+
         ConnectivityManager.NetworkCallback networkCallback =
                 new ConnectivityManager.NetworkCallback() {
                     @Override
-                    public void onAvailable(Network network) {
-                        logd("Wifi network available: " + network);
-                        sendRequestAsync(EVENT_WIFI_CONNECTIVITY_STATE_CHANGED, null);
+                    public void onCapabilitiesChanged(Network network,
+                            NetworkCapabilities networkCapabilities) {
+                        logd("onCapabilitiesChanged: " + networkCapabilities);
+                        sendRequestAsync(EVENT_WIFI_CONNECTIVITY_STATE_CHANGED,
+                                isInternetAvailable(networkCapabilities));
                     }
 
                     @Override
                     public void onLost(Network network) {
                         logd("Wifi network lost: " + network);
-                        sendRequestAsync(EVENT_WIFI_CONNECTIVITY_STATE_CHANGED, null);
-                    }
-
-                    @Override
-                    public void onUnavailable() {
-                        logd("Wifi network unavailable");
-                        sendRequestAsync(EVENT_WIFI_CONNECTIVITY_STATE_CHANGED, null);
+                        sendRequestAsync(EVENT_WIFI_CONNECTIVITY_STATE_CHANGED, false);
                     }
                 };
         mConnectivityManager.registerNetworkCallback(builder.build(), networkCallback);
@@ -560,15 +568,20 @@
 
     /**
      * Check whether Wi-Fi network is connected or not.
-     * @return {@code true} is Wi-Fi is connected, {@code false} otherwise.
+     * @return {@code true} is Wi-Fi is connected, and internet is available, {@code false}
+     * otherwise.
      */
     public boolean isWifiNetworkConnected() {
-        Network activeNetwork = mConnectivityManager.getActiveNetwork();
-        NetworkCapabilities networkCapabilities =
-                mConnectivityManager.getNetworkCapabilities(activeNetwork);
-        return networkCapabilities != null
-                && networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
-                && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        logd("isWifiNetworkConnected: " + mIsWifiNetworkConnected);
+        return mIsWifiNetworkConnected;
+    }
+
+    private boolean isInternetAvailable(NetworkCapabilities networkCapabilities) {
+        boolean isWifiConnected =
+                networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+        logd("isWifiConnected: " + isWifiConnected);
+        return isWifiConnected;
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java b/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java
index 0fac334..eea49e3 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaSubscriptionSourceManager.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.Flags;
 import com.android.telephony.Rlog;
 
 import java.util.concurrent.atomic.AtomicInteger;
@@ -63,6 +64,7 @@
 
     // Constructor
     private CdmaSubscriptionSourceManager(Context context, CommandsInterface ci) {
+        if (Flags.cleanupCdma()) return;
         mCi = ci;
         mCi.registerForCdmaSubscriptionChanged(this, EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED, null);
         mCi.registerForOn(this, EVENT_RADIO_ON, null);
@@ -97,7 +99,7 @@
         mCdmaSubscriptionSourceChangedRegistrants.remove(h);
         synchronized (sReferenceCountMonitor) {
             sReferenceCount--;
-            if (sReferenceCount <= 0) {
+            if (sReferenceCount <= 0 && mCi != null) {
                 mCi.unregisterForCdmaSubscriptionChanged(this);
                 mCi.unregisterForOn(this);
                 mCi.unregisterForSubscriptionStatusChanged(this);
@@ -112,6 +114,7 @@
      */
     @Override
     public void handleMessage(Message msg) {
+        if (Flags.cleanupCdma()) return;
         AsyncResult ar;
         switch (msg.what) {
             case EVENT_CDMA_SUBSCRIPTION_SOURCE_CHANGED:
@@ -165,6 +168,8 @@
      * @return Default CDMA subscription source from Settings DB if present.
      */
     public static int getDefault(Context context) {
+        if (Flags.cleanupCdma()) return Phone.CDMA_SUBSCRIPTION_UNKNOWN;
+
         // Get the default value from the Settings
         int subscriptionSource = Settings.Global.getInt(context.getContentResolver(),
                 Settings.Global.CDMA_SUBSCRIPTION_MODE, Phone.PREFERRED_CDMA_SUBSCRIPTION);
diff --git a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
index 3837790..8646981 100644
--- a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
+++ b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
@@ -190,7 +190,7 @@
     }
 
     /** Get the resolved carrier display name. */
-    public CarrierDisplayNameData getCarrierDisplayNameData() {
+    public @NonNull CarrierDisplayNameData getCarrierDisplayNameData() {
         resolveCarrierDisplayName();
         return mCarrierDisplayNameData;
     }
diff --git a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
index 1bbc692..2691eab 100644
--- a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
+++ b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
@@ -333,7 +333,7 @@
             log("onNetworkValidationRequested: networkCapability = ["
                     + DataUtils.networkCapabilityToString(networkCapability) + "]");
 
-            dnc.requestNetworkValidation(networkCapability, result -> post(() -> {
+            dnc.requestNetworkValidation(networkCapability, result -> {
                 try {
                     log("onNetworkValidationRequestDone:"
                             + DataServiceCallback.resultCodeToString(result));
@@ -342,7 +342,7 @@
                     // Ignore if the remote process is no longer available to call back.
                     loge("onNetworkValidationRequestDone RemoteException" + e);
                 }
-            }));
+            });
         }
 
         @Override
diff --git a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
index 7486b61..7131583 100644
--- a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
+++ b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
@@ -30,6 +30,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.os.AsyncResult;
 import android.os.Bundle;
 import android.os.Handler;
@@ -44,6 +45,7 @@
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.TelephonyDisplayInfo;
+import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
 
@@ -100,6 +102,38 @@
                     EVALUATION_REASON_VOICE_CALL_END})
     public @interface AutoDataSwitchEvaluationReason {}
 
+    /**
+     * Defines the switch type for considering a subscription as out of service before switching
+     * data, in milliseconds.
+     * If one SIM has service while the other is out of service for this duration,
+     * data will be switched to the SIM with service.
+     */
+    private static final int STABILITY_CHECK_AVAILABILITY_SWITCH = 0;
+    /**
+     * Defines the switch type for considering the RAT and signal strength advantage of a
+     * subscription to be stable before switching data, in milliseconds.
+     * Each RAT and signal strength is assigned a score. If one SIM's score is higher
+     * than the other SIM's score for this duration, data will be switched to that SIM.
+     */
+    private static final int STABILITY_CHECK_PERFORMANCE_SWITCH = 1;
+    /**
+     * Defines the switch type for switching data back to the default SIM when both SIMs are out of
+     * service, in milliseconds.
+     * If the current data is on the backup SIM and both SIMs remain out of service,
+     * data will be switched back to the default SIM.
+     */
+    private static final int STABILITY_CHECK_AVAILABILITY_SWITCH_BACK = 2;
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "STABILITY_CHECK_",
+            value = {STABILITY_CHECK_AVAILABILITY_SWITCH,
+                    STABILITY_CHECK_PERFORMANCE_SWITCH,
+                    STABILITY_CHECK_AVAILABILITY_SWITCH_BACK,
+            })
+    public @interface PreSwitchStabilityCheckType {}
+
+    /** stability check type to timer in milliseconds. */
+    private static final Map<Integer, Long> STABILITY_CHECK_TIMER_MAP = new ArrayMap<>();
+
     private static final String LOG_TAG = "ADSC";
 
     /** Event for service state changed. */
@@ -160,11 +194,22 @@
     /**
      * Event extras for checking environment stability.
      * @param targetPhoneId The target phone Id to switch to when the stability check pass.
-     * @param isForPerformance Whether the switch is due to RAT/signal strength performance.
+     * @param switchType Whether the switch is due to OOS, RAT/signal strength performance, or
+     *                   switch back.
      * @param needValidation Whether ping test needs to pass.
      */
-    private record StabilityEventExtra(int targetPhoneId, boolean isForPerformance,
-                               boolean needValidation) {}
+    private record StabilityEventExtra(int targetPhoneId,
+                                       @PreSwitchStabilityCheckType int switchType,
+                                       boolean needValidation) {
+        @Override
+        public String toString() {
+            return "StabilityEventExtra{"
+                    + "targetPhoneId=" + targetPhoneId
+                    + ", switchType=" + switchTypeToString(switchType)
+                    + ", needValidation=" + needValidation
+                    + "}";
+        }
+    }
 
     /**
      * Event extras for evaluating switch environment.
@@ -175,18 +220,6 @@
     /** {@code true} if we've displayed the notification the first time auto switch occurs **/
     private boolean mDisplayedNotification = false;
     /**
-     * Configurable time threshold in ms to define an internet connection status to be stable(e.g.
-     * out of service, in service, wifi is the default active network.etc), while -1 indicates auto
-     * switch feature disabled.
-     */
-    private long mAutoDataSwitchAvailabilityStabilityTimeThreshold = -1;
-    /**
-     * Configurable time threshold in ms to define an internet connection performance status to be
-     * stable (e.g. LTE + 4 signal strength, UMTS + 2 signal strength), while -1 indicates
-     * auto switch feature based on RAT/SS is disabled.
-     */
-    private long mAutoDataSwitchPerformanceStabilityTimeThreshold = -1;
-    /**
      * The tolerated gap of score for auto data switch decision, larger than which the device will
      * switch to the SIM with higher score. If 0, the device will always switch to the higher score
      * SIM. If < 0, the network type and signal strength based auto switch is disabled.
@@ -462,10 +495,14 @@
         mScoreTolerance =  dataConfig.getAutoDataSwitchScoreTolerance();
         mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired();
         mAllowNddsRoaming = dataConfig.doesAutoDataSwitchAllowRoaming();
-        mAutoDataSwitchAvailabilityStabilityTimeThreshold =
-                dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
-        mAutoDataSwitchPerformanceStabilityTimeThreshold =
-                dataConfig.getAutoDataSwitchPerformanceStabilityTimeThreshold();
+        STABILITY_CHECK_TIMER_MAP.put(STABILITY_CHECK_AVAILABILITY_SWITCH,
+                dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold());
+        STABILITY_CHECK_TIMER_MAP.put(STABILITY_CHECK_PERFORMANCE_SWITCH,
+                dataConfig.getAutoDataSwitchPerformanceStabilityTimeThreshold());
+        STABILITY_CHECK_TIMER_MAP.put(STABILITY_CHECK_AVAILABILITY_SWITCH_BACK,
+                dataConfig.getAutoDataSwitchAvailabilitySwitchbackStabilityTimeThreshold() >= 0
+                        ? dataConfig.getAutoDataSwitchAvailabilitySwitchbackStabilityTimeThreshold()
+                        : dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold());
         mAutoDataSwitchValidationMaxRetry =
                 dataConfig.getAutoDataSwitchValidationMaxRetry();
     }
@@ -628,7 +665,7 @@
      */
     public void evaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) {
         long delayMs = reason == EVALUATION_REASON_RETRY_VALIDATION
-                ? mAutoDataSwitchAvailabilityStabilityTimeThreshold
+                ? STABILITY_CHECK_TIMER_MAP.get(STABILITY_CHECK_AVAILABILITY_SWITCH)
                 << mAutoSwitchValidationFailedCount
                 : 0;
         if (!mScheduledEventsToExtras.containsKey(EVENT_EVALUATE_AUTO_SWITCH)) {
@@ -645,7 +682,7 @@
      */
     private void onEvaluateAutoDataSwitch(@AutoDataSwitchEvaluationReason int reason) {
         // auto data switch feature is disabled.
-        if (mAutoDataSwitchAvailabilityStabilityTimeThreshold < 0) return;
+        if (STABILITY_CHECK_TIMER_MAP.get(STABILITY_CHECK_AVAILABILITY_SWITCH) < 0) return;
         int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId();
         // check is valid DSDS
         if (mSubscriptionManagerService.getActiveSubIdList(true).length < 2) return;
@@ -669,7 +706,7 @@
             log(debugMessage.toString());
             if (res.targetPhoneId != INVALID_PHONE_INDEX) {
                 mSelectedTargetPhoneId = res.targetPhoneId;
-                startStabilityCheck(res.targetPhoneId, res.isForPerformance, res.needValidation);
+                startStabilityCheck(res.targetPhoneId, res.switchType, res.needValidation);
             } else {
                 cancelAnyPendingSwitch();
             }
@@ -690,8 +727,7 @@
                     log(debugMessage.append(
                             ", immediately back to default as user turns off default").toString());
                     return;
-                } else if (!(internetEvaluation = backupDataPhone.getDataNetworkController()
-                        .getInternetEvaluation(false/*ignoreExistingNetworks*/))
+                } else if (!(internetEvaluation = getInternetEvaluation(backupDataPhone))
                         .isSubsetOf(DataEvaluation.DataDisallowedReason.NOT_IN_SERVICE)) {
                     mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(
                             DEFAULT_PHONE_INDEX, EVALUATION_REASON_DATA_SETTINGS_CHANGED);
@@ -711,7 +747,7 @@
             }
 
             boolean backToDefault = false;
-            boolean isForPerformance = false;
+            int switchType = STABILITY_CHECK_AVAILABILITY_SWITCH;
             boolean needValidation = true;
 
             if (isNddsRoamingEnabled()) {
@@ -755,7 +791,7 @@
                                             .append(defaultScore).append(" versus current ")
                                             .append(currentScore);
                                     backToDefault = true;
-                                    isForPerformance = true;
+                                    switchType = STABILITY_CHECK_PERFORMANCE_SWITCH;
                                     needValidation = mRequirePingTestBeforeSwitch;
                                 }
                             } else {
@@ -767,6 +803,7 @@
                         } else {
                             debugMessage.append(", back to default as both phones are unusable.");
                             backToDefault = true;
+                            switchType = STABILITY_CHECK_AVAILABILITY_SWITCH_BACK;
                             needValidation = false;
                         }
                     }
@@ -790,7 +827,7 @@
                                 .append(defaultScore).append(" versus current ")
                                 .append(currentScore);
                         backToDefault = true;
-                        isForPerformance = true;
+                        switchType = STABILITY_CHECK_PERFORMANCE_SWITCH;
                         needValidation = mRequirePingTestBeforeSwitch;
                     }
                 } else if (isInService(mPhonesSignalStatus[defaultDataPhoneId].mDataRegState)) {
@@ -803,7 +840,7 @@
             if (backToDefault) {
                 log(debugMessage.toString());
                 mSelectedTargetPhoneId = defaultDataPhoneId;
-                startStabilityCheck(DEFAULT_PHONE_INDEX, isForPerformance, needValidation);
+                startStabilityCheck(DEFAULT_PHONE_INDEX, switchType, needValidation);
             } else {
                 // cancel any previous attempts of switching back to default phone
                 cancelAnyPendingSwitch();
@@ -820,9 +857,9 @@
     @NonNull private StabilityEventExtra evaluateAnyCandidateToUse(int defaultPhoneId,
             @NonNull StringBuilder debugMessage) {
         Phone defaultDataPhone = PhoneFactory.getPhone(defaultPhoneId);
-        boolean isForPerformance = false;
+        int switchType = STABILITY_CHECK_AVAILABILITY_SWITCH;
         StabilityEventExtra invalidResult = new StabilityEventExtra(INVALID_PHONE_INDEX,
-                isForPerformance, mRequirePingTestBeforeSwitch);
+                switchType, mRequirePingTestBeforeSwitch);
 
         if (defaultDataPhone == null) {
             debugMessage.append(", no candidate as no sim loaded");
@@ -882,7 +919,7 @@
                         debugMessage.append(" with ").append(defaultScore)
                                 .append(" versus candidate higher score ").append(candidateScore);
                         secondaryDataPhone = PhoneFactory.getPhone(phoneId);
-                        isForPerformance = true;
+                        switchType = STABILITY_CHECK_PERFORMANCE_SWITCH;
                     } else {
                         debugMessage.append(", candidate's score ").append(candidateScore)
                                 .append(" doesn't justify the switch given the current ")
@@ -903,7 +940,7 @@
                             debugMessage.append(" with higher score ").append(candidateScore)
                                     .append(" versus current ").append(defaultScore);
                             secondaryDataPhone = PhoneFactory.getPhone(phoneId);
-                            isForPerformance = true;
+                            switchType = STABILITY_CHECK_PERFORMANCE_SWITCH;
                         } else {
                             debugMessage.append(", but its score ").append(candidateScore)
                                     .append(" doesn't meet the bar to switch given the current ")
@@ -917,15 +954,14 @@
             }
 
             if (secondaryDataPhone != null) {
+                DataEvaluation evaluation = getInternetEvaluation(secondaryDataPhone);
                 // check internet data is allowed on the candidate
-                DataEvaluation internetEvaluation = secondaryDataPhone.getDataNetworkController()
-                        .getInternetEvaluation(false/*ignoreExistingNetworks*/);
-                if (!internetEvaluation.containsDisallowedReasons()) {
+                if (!evaluation.containsDisallowedReasons()) {
                     return new StabilityEventExtra(phoneId,
-                            isForPerformance, mRequirePingTestBeforeSwitch);
+                            switchType, mRequirePingTestBeforeSwitch);
                 } else {
                     debugMessage.append(", but candidate's data is not allowed ")
-                            .append(internetEvaluation);
+                            .append(evaluation);
                 }
             }
         }
@@ -934,10 +970,31 @@
     }
 
     /**
+     * Get internet evaluation base on phone's satellite/terrestrial env.
+     * @param phone the target phone
+     * @return internet evaluation.
+     */
+    @NonNull
+    private DataEvaluation getInternetEvaluation(@NonNull Phone phone) {
+        NetworkRequest.Builder reqBuilder = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+
+        if (phone.getServiceState().isUsingNonTerrestrialNetwork()) {
+            // When satellite, RCS requests are restricted.
+            reqBuilder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        }
+
+        return phone.getDataNetworkController().evaluateNetworkRequest(
+                new TelephonyNetworkRequest(reqBuilder.build(), phone, sFeatureFlags),
+                DataEvaluation.DataEvaluationReason.EXTERNAL_QUERY);
+    }
+
+    /**
      * @return {@code true} If the feature of switching base on RAT and signal strength is enabled.
      */
     private boolean isRatSignalStrengthBasedSwitchEnabled() {
-        return mScoreTolerance >= 0 && mAutoDataSwitchPerformanceStabilityTimeThreshold >= 0;
+        return mScoreTolerance >= 0
+                && STABILITY_CHECK_TIMER_MAP.get(STABILITY_CHECK_PERFORMANCE_SWITCH) >= 0;
     }
 
     /**
@@ -950,29 +1007,27 @@
     /**
      * 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.
+     * {@link #STABILITY_CHECK_TIMER_MAP} MS.
      * @param targetPhoneId the target phone Id.
-     * @param isForPerformance {@code true} entails longer stability check.
+     * @param switchType {@code true} determines stability check timer.
      * @param needValidation {@code true} if validation is needed.
      */
-    private void startStabilityCheck(int targetPhoneId, boolean isForPerformance,
+    private void startStabilityCheck(int targetPhoneId, @PreSwitchStabilityCheckType int switchType,
             boolean needValidation) {
         StabilityEventExtra eventExtras = (StabilityEventExtra)
                 mScheduledEventsToExtras.getOrDefault(EVENT_STABILITY_CHECK_PASSED,
-                        new StabilityEventExtra(INVALID_PHONE_INDEX, false /*need validation*/,
+                        new StabilityEventExtra(INVALID_PHONE_INDEX, -1 /*invalid switch type*/,
                                 false /*isForPerformance*/));
         long delayMs = -1;
         // Check if already scheduled one with that combination of extras.
         if (eventExtras.targetPhoneId != targetPhoneId
                 || eventExtras.needValidation != needValidation
-                || eventExtras.isForPerformance != isForPerformance) {
+                || eventExtras.switchType != switchType) {
             eventExtras =
-                    new StabilityEventExtra(targetPhoneId, isForPerformance, needValidation);
+                    new StabilityEventExtra(targetPhoneId, switchType, needValidation);
 
             // Reset with new timer.
-            delayMs = isForPerformance
-                    ? mAutoDataSwitchPerformanceStabilityTimeThreshold
-                    : mAutoDataSwitchAvailabilityStabilityTimeThreshold;
+            delayMs = STABILITY_CHECK_TIMER_MAP.get(switchType);
             scheduleEventWithTimer(EVENT_STABILITY_CHECK_PASSED, eventExtras, delayMs);
         }
         log("startStabilityCheck: "
@@ -1165,6 +1220,17 @@
         return phoneId >= 0 && phoneId < mPhonesSignalStatus.length;
     }
 
+    /** Auto data switch stability check type to string. */
+    @NonNull
+    public static String switchTypeToString(@PreSwitchStabilityCheckType int switchType) {
+        return switch (switchType) {
+            case STABILITY_CHECK_AVAILABILITY_SWITCH -> "AVAILABILITY_SWITCH";
+            case STABILITY_CHECK_PERFORMANCE_SWITCH -> "PERFORMANCE_SWITCH";
+            case STABILITY_CHECK_AVAILABILITY_SWITCH_BACK -> "AVAILABILITY_SWITCH_BACK";
+            default -> "Unknown(" + switchType + ")";
+        };
+    }
+
     /**
      * Log debug messages.
      * @param s debug messages
@@ -1205,8 +1271,9 @@
         pw.println("mAutoDataSwitchValidationMaxRetry=" + mAutoDataSwitchValidationMaxRetry
                 + " mAutoSwitchValidationFailedCount=" + mAutoSwitchValidationFailedCount);
         pw.println("mRequirePingTestBeforeDataSwitch=" + mRequirePingTestBeforeSwitch);
-        pw.println("mAutoDataSwitchAvailabilityStabilityTimeThreshold="
-                + mAutoDataSwitchAvailabilityStabilityTimeThreshold);
+        pw.println("STABILITY_CHECK_TIMER_MAP:");
+        STABILITY_CHECK_TIMER_MAP.forEach((key, value)
+                -> pw.println(switchTypeToString(key) + ": " + value));
         pw.println("mSelectedTargetPhoneId=" + mSelectedTargetPhoneId);
         pw.increaseIndent();
         for (PhoneSignalStatus status: mPhonesSignalStatus) {
diff --git a/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java b/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java
index ad1a8aa..026fbf4 100644
--- a/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java
+++ b/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java
@@ -268,7 +268,8 @@
 
         mNetworkCallback = new ConnectivityNetworkCallback(subId);
 
-        mConnectivityManager.requestNetwork(createNetworkRequest(), mNetworkCallback, mHandler);
+        mConnectivityManager.requestNetwork(
+                createNetworkRequest(subId), mNetworkCallback, mHandler);
         mHandler.postDelayed(() -> onValidationTimeout(subId), timeoutInMs);
     }
 
@@ -309,13 +310,22 @@
         return mState != STATE_IDLE;
     }
 
-    private NetworkRequest createNetworkRequest() {
-        return new NetworkRequest.Builder()
+    private NetworkRequest createNetworkRequest(int subId) {
+        NetworkRequest.Builder req = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                 .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
-                        .setSubscriptionId(mSubId).build())
-                .build();
+                        .setSubscriptionId(subId).build());
+
+        // Satellite is considered valid as long as it can serve restricted requests.
+        Phone target = PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
+        boolean isSatellite = target != null
+                && target.getServiceState().isUsingNonTerrestrialNetwork();
+        if (isSatellite) {
+            req.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE)
+                    .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        }
+        return req.build();
     }
 
     private synchronized void reportValidationResult(boolean passed, int subId) {
diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java
index 89b0fec..6dec224 100644
--- a/src/java/com/android/internal/telephony/data/DataConfigManager.java
+++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java
@@ -1151,6 +1151,19 @@
     }
 
     /**
+     * Defines the threshold for switching data back to the default SIM when both SIMs are out of
+     * service, in milliseconds.
+     * If the current data is on the backup SIM and both SIMs remain out of service for this
+     * duration, data will be switched back to the default SIM.
+     * A value of 0 means an immediate switch. If the value is negative, the threshold defined by
+     * {@link #getAutoDataSwitchAvailabilityStabilityTimeThreshold()} will be used instead.
+     */
+    public long getAutoDataSwitchAvailabilitySwitchbackStabilityTimeThreshold() {
+        return mResources.getInteger(com.android.internal.R.integer
+                .auto_data_switch_availability_switchback_stability_time_threshold_millis);
+    }
+
+    /**
      * Get the TCP config string, used by {@link LinkProperties#setTcpBufferSizes(String)}.
      * The config string will have the following form, with values in bytes:
      * "read_min,read_default,read_max,write_min,write_default,write_max"
diff --git a/src/java/com/android/internal/telephony/data/DataEvaluation.java b/src/java/com/android/internal/telephony/data/DataEvaluation.java
index 40c0081..f5eae91 100644
--- a/src/java/com/android/internal/telephony/data/DataEvaluation.java
+++ b/src/java/com/android/internal/telephony/data/DataEvaluation.java
@@ -225,8 +225,6 @@
         SIM_LOADED(true),
         /** SIM is removed. */
         SIM_REMOVAL(true),
-        /** SIM is disabled. */
-        SIM_DISABLED(true),
         /** Data profiles changed. */
         DATA_PROFILES_CHANGED(true),
         /** When service state changes.(For now only considering data RAT and data registration). */
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 9e432e1..b5bfc1d 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -1580,7 +1580,8 @@
      * @return The data evaluation result.
      */
     @NonNull
-    private DataEvaluation evaluateNetworkRequest(
+    @VisibleForTesting
+    public DataEvaluation evaluateNetworkRequest(
             @NonNull TelephonyNetworkRequest networkRequest, DataEvaluationReason reason) {
         DataEvaluation evaluation = new DataEvaluation(reason);
         int transport = mAccessNetworksManager.getPreferredTransportByNetworkCapability(
@@ -2235,6 +2236,9 @@
             }
             // When the device is on satellite, internet with restricted capabilities always honor
             // soft disallowed reasons and not respected as restricted request
+            // Note - ping test are performed with restricted request on satellite assuming they cannot
+            // bypass any checks. If below is removed, reevaluate the ping request in
+            // CellularNetworkValidator and the getInternetEvaluation in AutoDataSwitchController
             return !(mServiceState.isUsingNonTerrestrialNetwork()
                     && networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
 
@@ -3543,6 +3547,15 @@
     }
 
     /**
+     * Called when SIM is absent.
+     */
+    private void onSimAbsent() {
+        log("onSimAbsent");
+        sendMessage(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
+                DataEvaluationReason.SIM_REMOVAL));
+    }
+
+    /**
      * Called when SIM state changes.
      *
      * @param simState SIM state. (Note this is mixed with card state and application state.)
@@ -3550,22 +3563,13 @@
     private void onSimStateChanged(@SimState int simState) {
         log("onSimStateChanged: state=" + TelephonyManager.simStateToString(simState));
         if (mSimState != simState) {
+            mSimState = simState;
             if (simState == TelephonyManager.SIM_STATE_ABSENT) {
-                log("onSimStateChanged: SIM absent.");
-                sendMessage(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
-                        DataEvaluationReason.SIM_REMOVAL));
-            } else if (simState == TelephonyManager.SIM_STATE_NOT_READY
-                    && mSimState == TelephonyManager.SIM_STATE_LOADED) {
-                if (mFeatureFlags.simDisabledGracefulTearDown()) {
-                    log("onSimStateChanged: SIM disabled.");
-                    sendMessage(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
-                            DataEvaluationReason.SIM_DISABLED));
-                }
+                onSimAbsent();
             } else if (simState == TelephonyManager.SIM_STATE_LOADED) {
                 sendMessage(obtainMessage(EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS,
                         DataEvaluationReason.SIM_LOADED));
             }
-            mSimState = simState;
             mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
                     () -> callback.onSimStateChanged(mSimState)));
         }
diff --git a/src/java/com/android/internal/telephony/data/DataUtils.java b/src/java/com/android/internal/telephony/data/DataUtils.java
index 20da97f..c88e0b3 100644
--- a/src/java/com/android/internal/telephony/data/DataUtils.java
+++ b/src/java/com/android/internal/telephony/data/DataUtils.java
@@ -287,6 +287,8 @@
             case NetworkCapabilities.NET_CAPABILITY_VSIM -> ApnSetting.TYPE_VSIM;
             case NetworkCapabilities.NET_CAPABILITY_BIP -> ApnSetting.TYPE_BIP;
             case NetworkCapabilities.NET_CAPABILITY_RCS -> ApnSetting.TYPE_RCS;
+            case NetworkCapabilities.NET_CAPABILITY_OEM_PAID -> ApnSetting.TYPE_OEM_PAID;
+            case NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE -> ApnSetting.TYPE_OEM_PRIVATE;
             default -> ApnSetting.TYPE_NONE;
         };
     }
@@ -315,6 +317,8 @@
             case ApnSetting.TYPE_VSIM -> NetworkCapabilities.NET_CAPABILITY_VSIM;
             case ApnSetting.TYPE_ENTERPRISE -> NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
             case ApnSetting.TYPE_RCS -> NetworkCapabilities.NET_CAPABILITY_RCS;
+            case ApnSetting.TYPE_OEM_PAID -> NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+            case ApnSetting.TYPE_OEM_PRIVATE -> NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
             default -> -1;
         };
     }
diff --git a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
index 1005bb7..079e705 100644
--- a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
@@ -1491,14 +1491,18 @@
             mPreferredDataPhoneId = mEmergencyOverride.mPhoneId;
             mLastSwitchPreferredDataReason = DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN;
         } else {
-            int imsRegTech = mImsRegTechProvider.get(mContext, mPhoneIdInVoiceCall);
-            if (isAnyVoiceCallActiveOnDevice() && imsRegTech != REGISTRATION_TECH_IWLAN) {
-                if (imsRegTech != REGISTRATION_TECH_CROSS_SIM) {
-                    mPreferredDataPhoneId = shouldSwitchDataDueToInCall()
-                            ? mPhoneIdInVoiceCall : getFallbackDataPhoneIdForInternetRequests();
+            if (isAnyVoiceCallActiveOnDevice()) {
+                int imsRegTech = mImsRegTechProvider.get(mContext, mPhoneIdInVoiceCall);
+                if (imsRegTech != REGISTRATION_TECH_IWLAN) {
+                    if (imsRegTech != REGISTRATION_TECH_CROSS_SIM) {
+                        mPreferredDataPhoneId = shouldSwitchDataDueToInCall()
+                                ? mPhoneIdInVoiceCall : getFallbackDataPhoneIdForInternetRequests();
+                    } else {
+                        logl("IMS call on cross-SIM, skip switching data to phone "
+                                + mPhoneIdInVoiceCall);
+                    }
                 } else {
-                    logl("IMS call on cross-SIM, skip switching data to phone "
-                            + mPhoneIdInVoiceCall);
+                    mPreferredDataPhoneId = getFallbackDataPhoneIdForInternetRequests();
                 }
             } else {
                 mPreferredDataPhoneId = getFallbackDataPhoneIdForInternetRequests();
@@ -1830,16 +1834,18 @@
         if (phone == null) {
             return false;
         }
-
+        Call bgCall = phone.getBackgroundCall();
+        Call fgCall = phone.getForegroundCall();
+        if (bgCall == null || fgCall == null) {
+            return false;
+        }
         // A phone in voice call might trigger data being switched to it.
         // Exclude dialing to give modem time to process an EMC first before dealing with DDS switch
         // Include alerting because modem RLF leads to delay in switch, so carrier required to
         // switch in alerting phase.
         // TODO: check ringing call for vDADA
-        return (!phone.getBackgroundCall().isIdle()
-                && phone.getBackgroundCall().getState() != Call.State.DIALING)
-                || (!phone.getForegroundCall().isIdle()
-                && phone.getForegroundCall().getState() != Call.State.DIALING);
+        return (!bgCall.isIdle() && bgCall.getState() != Call.State.DIALING)
+                || (!fgCall.isIdle() && fgCall.getState() != Call.State.DIALING);
     }
 
     private void updateHalCommandToUse() {
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
index 677db87..681ca12 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
@@ -783,6 +783,20 @@
             dispose();
             return;
         }
+
+        if (scanType == DomainSelectionService.SCAN_TYPE_FULL_SERVICE) {
+            // Handle as unknown network.
+            EmergencyRegistrationResult result = new EmergencyRegistrationResult(
+                    AccessNetworkConstants.AccessNetworkType.UNKNOWN,
+                    NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN,
+                    NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0, "", "", "");
+
+            if (mHandler != null) {
+                Message msg = mHandler.obtainMessage(EVENT_EMERGENCY_NETWORK_SCAN_RESULT,
+                        new AsyncResult(null, result, null));
+                msg.sendToTarget();
+            }
+        }
     }
 
     private void onModemReset() {
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
index f983108..b3e8095 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
@@ -20,6 +20,12 @@
 import static android.telecom.Connection.STATE_DISCONNECTED;
 import static android.telephony.CarrierConfigManager.ImsEmergency.KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL;
+import static android.telephony.TelephonyManager.EMERGENCY_CALLBACK_MODE_CALL;
+import static android.telephony.TelephonyManager.EMERGENCY_CALLBACK_MODE_SMS;
+import static android.telephony.TelephonyManager.STOP_REASON_EMERGENCY_SMS_SENT;
+import static android.telephony.TelephonyManager.STOP_REASON_OUTGOING_EMERGENCY_CALL_INITIATED;
+import static android.telephony.TelephonyManager.STOP_REASON_TIMER_EXPIRED;
+import static android.telephony.TelephonyManager.STOP_REASON_UNKNOWN;
 
 import static com.android.internal.telephony.TelephonyIntents.ACTION_EMERGENCY_CALL_STATE_CHANGED;
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_CALLBACK;
@@ -65,6 +71,7 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.data.PhoneSwitcher;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
 import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.telephony.Rlog;
@@ -102,8 +109,6 @@
      * from the modem.
      */
     private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1 * 1000;
-    @VisibleForTesting
-    public static final int DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS = 3 * 1000;
     /** Default value for if Emergency Callback Mode is supported. */
     private static final boolean DEFAULT_EMERGENCY_CALLBACK_MODE_SUPPORTED = true;
     /** Default Emergency Callback Mode exit timeout value. */
@@ -135,6 +140,7 @@
     private final CarrierConfigManager mConfigManager;
     private final Handler mHandler;
     private final boolean mIsSuplDdsSwitchRequiredForEmergencyCall;
+    private final int mWaitForInServiceTimeoutMs;
     private final PowerManager.WakeLock mWakeLock;
     private RadioOnHelper mRadioOnHelper;
     @EmergencyConstants.EmergencyMode
@@ -148,7 +154,8 @@
     /** For emergency calls */
     private final long mEcmExitTimeoutMs;
     // A runnable which is used to automatically exit from Ecm after a period of time.
-    private final Runnable mExitEcmRunnable = this::exitEmergencyCallbackMode;
+    private final Runnable mExitEcmRunnable = () -> exitEmergencyCallbackMode(
+            STOP_REASON_TIMER_EXPIRED);
     // Tracks emergency calls by callId that have reached {@link Call.State#ACTIVE}.
     private final Set<android.telecom.Connection> mActiveEmergencyCalls = new ArraySet<>();
     private Phone mPhone;
@@ -184,6 +191,8 @@
     private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
             (slotIndex, subId, carrierId, specificCarrierId) -> onCarrierConfigurationChanged(
                     slotIndex, subId);
+    /** Feature flags */
+    private final FeatureFlags mFeatureFlags;
 
     /**
      * Listens for Emergency Callback Mode state change intents
@@ -326,12 +335,14 @@
                                     if (!isSamePhone(mPhone, mSmsPhone)) {
                                         completeEmergencyMode(emergencyType,
                                                 DisconnectCause.OUTGOING_EMERGENCY_CALL_PLACED);
-                                        exitEmergencySmsCallbackMode();
+                                        exitEmergencySmsCallbackMode(
+                                                STOP_REASON_OUTGOING_EMERGENCY_CALL_INITIATED);
                                     }
                                 } else {
                                     completeEmergencyMode(emergencyType);
                                     mIsEmergencyCallStartedDuringEmergencySms = false;
-                                    exitEmergencySmsCallbackMode();
+                                    exitEmergencySmsCallbackMode(
+                                            STOP_REASON_OUTGOING_EMERGENCY_CALL_INITIATED);
                                     turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
                                             mIsTestEmergencyNumber);
                                 }
@@ -343,7 +354,8 @@
 
                             if (mIsEmergencyCallStartedDuringEmergencySms) {
                                 mIsEmergencyCallStartedDuringEmergencySms = false;
-                                exitEmergencySmsCallbackMode();
+                                exitEmergencySmsCallbackMode(
+                                        STOP_REASON_OUTGOING_EMERGENCY_CALL_INITIATED);
                                 turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
                                         mIsTestEmergencyNumber);
                             }
@@ -409,7 +421,8 @@
                         // the emergency call was started, needs to exit the emergency mode first.
                         if (mIsEmergencyCallStartedDuringEmergencySms) {
                             final Phone smsPhone = mSmsPhone;
-                            exitEmergencySmsCallbackMode();
+                            exitEmergencySmsCallbackMode(
+                                    STOP_REASON_OUTGOING_EMERGENCY_CALL_INITIATED);
 
                             if (mPhone != null && smsPhone != null
                                     && !isSamePhone(mPhone, smsPhone)) {
@@ -428,7 +441,7 @@
                     break;
                 }
                 case MSG_EXIT_SCBM: {
-                    exitEmergencySmsCallbackModeAndEmergencyMode();
+                    exitEmergencySmsCallbackModeAndEmergencyMode(STOP_REASON_TIMER_EXPIRED);
                     break;
                 }
                 case MSG_NEW_RINGING_CONNECTION: {
@@ -457,11 +470,13 @@
      * @param context                                 The context of the application.
      * @param isSuplDdsSwitchRequiredForEmergencyCall Whether gnss supl requires default data for
      *                                                emergency call.
+     * @param featureFlags                            The telephony feature flags.
      */
-    public static void make(Context context, boolean isSuplDdsSwitchRequiredForEmergencyCall) {
+    public static void make(Context context, boolean isSuplDdsSwitchRequiredForEmergencyCall,
+            int waitForInServiceTimeout, @NonNull FeatureFlags featureFlags) {
         if (INSTANCE == null) {
             INSTANCE = new EmergencyStateTracker(context, Looper.myLooper(),
-                    isSuplDdsSwitchRequiredForEmergencyCall);
+                    isSuplDdsSwitchRequiredForEmergencyCall, waitForInServiceTimeout, featureFlags);
         }
     }
 
@@ -481,12 +496,14 @@
      * Initializes EmergencyStateTracker.
      */
     private EmergencyStateTracker(Context context, Looper looper,
-            boolean isSuplDdsSwitchRequiredForEmergencyCall) {
+            boolean isSuplDdsSwitchRequiredForEmergencyCall, int waitForInServiceTimeout,
+            @NonNull FeatureFlags featureFlags) {
         mEcmExitTimeoutMs = DEFAULT_ECM_EXIT_TIMEOUT_MS;
         mContext = context;
         mHandler = new MyHandler(looper);
         mIsSuplDdsSwitchRequiredForEmergencyCall = isSuplDdsSwitchRequiredForEmergencyCall;
-
+        mWaitForInServiceTimeoutMs = waitForInServiceTimeout;
+        mFeatureFlags = featureFlags;
         PowerManager pm = context.getSystemService(PowerManager.class);
         mWakeLock = (pm != null) ? pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                 "telephony:" + TAG) : null;
@@ -518,25 +535,33 @@
      * @param looper                                  The {@link Looper} of the application.
      * @param isSuplDdsSwitchRequiredForEmergencyCall Whether gnss supl requires default data for
      *                                                emergency call.
+     * @param waitForInServiceTimeout                 The timeout duration how long does it wait for
+     *                                                modem to get in-service state when emergency
+     *                                                call is dialed in airplane mode before
+     *                                                starting the emergency call.
      * @param phoneFactoryProxy                       The {@link PhoneFactoryProxy} to be injected.
      * @param phoneSwitcherProxy                      The {@link PhoneSwitcherProxy} to be injected.
      * @param telephonyManagerProxy                   The {@link TelephonyManagerProxy} to be
      *                                                injected.
      * @param radioOnHelper                           The {@link RadioOnHelper} to be injected.
+     * @param featureFlags                            The {@link FeatureFlags} to be injected.
      */
     @VisibleForTesting
     public EmergencyStateTracker(Context context, Looper looper,
-            boolean isSuplDdsSwitchRequiredForEmergencyCall, PhoneFactoryProxy phoneFactoryProxy,
-            PhoneSwitcherProxy phoneSwitcherProxy, TelephonyManagerProxy telephonyManagerProxy,
-            RadioOnHelper radioOnHelper, long ecmExitTimeoutMs) {
+            boolean isSuplDdsSwitchRequiredForEmergencyCall, int waitForInServiceTimeout,
+            PhoneFactoryProxy phoneFactoryProxy, PhoneSwitcherProxy phoneSwitcherProxy,
+            TelephonyManagerProxy telephonyManagerProxy, RadioOnHelper radioOnHelper,
+            long ecmExitTimeoutMs, FeatureFlags featureFlags) {
         mContext = context;
         mHandler = new MyHandler(looper);
         mIsSuplDdsSwitchRequiredForEmergencyCall = isSuplDdsSwitchRequiredForEmergencyCall;
+        mWaitForInServiceTimeoutMs = waitForInServiceTimeout;
         mPhoneFactoryProxy = phoneFactoryProxy;
         mPhoneSwitcherProxy = phoneSwitcherProxy;
         mTelephonyManagerProxy = telephonyManagerProxy;
         mRadioOnHelper = radioOnHelper;
         mEcmExitTimeoutMs = ecmExitTimeoutMs;
+        mFeatureFlags = featureFlags;
         mWakeLock = null; // Don't declare a wakelock in tests
         mConfigManager = context.getSystemService(CarrierConfigManager.class);
         mConfigManager.registerCarrierConfigChangeListener(mHandler::post,
@@ -574,7 +599,7 @@
             // 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();
+                exitEmergencySmsCallbackMode(STOP_REASON_OUTGOING_EMERGENCY_CALL_INITIATED);
                 mOngoingConnection = c;
                 mIsTestEmergencyNumber = isTestEmergencyNumber;
                 if (isInEcm()) {
@@ -583,6 +608,11 @@
                     releaseWakeLock();
                     ((GsmCdmaPhone) mPhone).notifyEcbmTimerReset(Boolean.TRUE);
 
+                    if (mFeatureFlags.emergencyCallbackModeNotification()) {
+                        mPhone.stopEmergencyCallbackMode(EMERGENCY_CALLBACK_MODE_CALL,
+                                STOP_REASON_OUTGOING_EMERGENCY_CALL_INITIATED);
+                    }
+
                     mOngoingCallProperties = 0;
                     mCallEmergencyModeFuture = new CompletableFuture<>();
                     setEmergencyMode(mPhone, EMERGENCY_TYPE_CALL, MODE_EMERGENCY_WWAN,
@@ -622,7 +652,7 @@
                     mIsEmergencyCallStartedDuringEmergencySms = false;
                 }
 
-                exitEmergencySmsCallbackMode();
+                exitEmergencySmsCallbackMode(STOP_REASON_OUTGOING_EMERGENCY_CALL_INITIATED);
             }
 
             if (mIsEmergencyCallStartedDuringEmergencySms) {
@@ -1087,7 +1117,7 @@
      * Handles the radio power off request.
      */
     public void onCellularRadioPowerOffRequested() {
-        exitEmergencySmsCallbackModeAndEmergencyMode();
+        exitEmergencySmsCallbackModeAndEmergencyMode(STOP_REASON_UNKNOWN);
         exitEmergencyCallbackMode();
     }
 
@@ -1159,10 +1189,14 @@
 
         setEmergencyCallbackMode(mPhone, EMERGENCY_TYPE_CALL);
 
-        // Post this runnable so we will automatically exit if no one invokes
-        // exitEmergencyCallbackMode() directly.
         long delayInMillis = TelephonyProperties.ecm_exit_timer()
                 .orElse(mEcmExitTimeoutMs);
+        if (mFeatureFlags.emergencyCallbackModeNotification()) {
+            mPhone.startEmergencyCallbackMode(EMERGENCY_CALLBACK_MODE_CALL, delayInMillis);
+        }
+
+        // Post this runnable so we will automatically exit if no one invokes
+        // exitEmergencyCallbackMode() directly.
         mHandler.postDelayed(mExitEcmRunnable, delayInMillis);
 
         // We don't want to go to sleep while in ECM.
@@ -1170,9 +1204,27 @@
     }
 
     /**
-     * Exits emergency callback mode and notifies relevant listeners.
+     * Exits the emergency callback mode.
+     *
+     * <p>This method exits the emergency callback mode with an unknown stop reason. It removes
+     * any pending exit requests and notifies relevant listeners about the change in status.
      */
     public void exitEmergencyCallbackMode() {
+        exitEmergencyCallbackMode(STOP_REASON_UNKNOWN);
+    }
+
+    /**
+     * Exits the emergency callback mode.
+     *
+     * <p>This method exits the emergency callback mode with the specified stop reason. It removes
+     * any pending exit requests and notifies relevant listeners about the change in status,
+     * providing the reason for exiting.
+     *
+     * @param reason The reason for exiting. See
+     *               {@link TelephonyManager.EmergencyCallbackModeStopReason} for possible values.
+     */
+    public void exitEmergencyCallbackMode(
+            @TelephonyManager.EmergencyCallbackModeStopReason int reason) {
         Rlog.d(TAG, "exit ECBM");
         // Remove pending exit ECM runnable, if any.
         mHandler.removeCallbacks(mExitEcmRunnable);
@@ -1191,6 +1243,10 @@
             sendEmergencyCallbackModeChange();
             gsmCdmaPhone.notifyEmergencyCallRegistrants(false);
 
+            if (mFeatureFlags.emergencyCallbackModeNotification()) {
+                gsmCdmaPhone.stopEmergencyCallbackMode(EMERGENCY_CALLBACK_MODE_CALL, reason);
+            }
+
             // Exit emergency mode on modem.
             exitEmergencyMode(gsmCdmaPhone, EMERGENCY_TYPE_CALL);
         }
@@ -1213,11 +1269,20 @@
     }
 
     /**
-     * Exits emergency callback mode and triggers runnable after exit response is received.
+     * Exits the emergency callback mode and triggers {@link Runnable} after exit response is
+     * received.
+     *
+     * <p>This method exits the emergency callback mode with the specified stop reason and then
+     * executes the provided {@link Runnable}.
+     *
+     * @param onComplete The {@link Runnable} to execute after exiting emergency callback mode.
+     * @param reason The reason for exiting. See
+     *               {@link TelephonyManager.EmergencyCallbackModeStopReason} for possible values.
      */
-    public void exitEmergencyCallbackMode(Runnable onComplete) {
+    public void exitEmergencyCallbackMode(Runnable onComplete,
+            @TelephonyManager.EmergencyCallbackModeStopReason int reason) {
         mOnEcmExitCompleteRunnable = onComplete;
-        exitEmergencyCallbackMode();
+        exitEmergencyCallbackMode(reason);
     }
 
     /**
@@ -1323,7 +1388,7 @@
                 // emergency SMS callback mode first.
                 exitScbmInOtherPhone = true;
                 mIsEmergencySmsStartedDuringScbm = true;
-                exitEmergencySmsCallbackModeAndEmergencyMode();
+                exitEmergencySmsCallbackModeAndEmergencyMode(STOP_REASON_EMERGENCY_SMS_SENT);
             } else {
                 Rlog.e(TAG, "Emergency SMS is in progress on the other slot.");
                 return CompletableFuture.completedFuture(DisconnectCause.ERROR_UNSPECIFIED);
@@ -1460,6 +1525,9 @@
      */
     private void enterEmergencySmsCallbackMode() {
         Rlog.d(TAG, "enter SCBM while " + (isInScbm() ? "in" : "not in") + " SCBM");
+
+        boolean shouldRestartEcm = isInScbm();
+
         // Remove pending message if present.
         mHandler.removeMessages(MSG_EXIT_SCBM);
 
@@ -1484,17 +1552,29 @@
         // Post the message so we will automatically exit if no one invokes
         // exitEmergencySmsCallbackModeAndEmergencyMode() directly.
         mHandler.sendEmptyMessageDelayed(MSG_EXIT_SCBM, delayInMillis);
+
+        if (mFeatureFlags.emergencyCallbackModeNotification()) {
+            if (shouldRestartEcm) {
+                mSmsPhone.restartEmergencyCallbackMode(EMERGENCY_CALLBACK_MODE_SMS, delayInMillis);
+            } else {
+                mSmsPhone.startEmergencyCallbackMode(EMERGENCY_CALLBACK_MODE_SMS, delayInMillis);
+            }
+        }
     }
 
     /**
      * Exits emergency SMS callback mode and emergency mode if the device is in SCBM and
      * the emergency mode is in CALLBACK.
+     *
+     * @param reason The reason for exiting. See
+     *               {@link TelephonyManager.EmergencyCallbackModeStopReason} for possible values.
      */
-    private void exitEmergencySmsCallbackModeAndEmergencyMode() {
+    private void exitEmergencySmsCallbackModeAndEmergencyMode(
+            @TelephonyManager.EmergencyCallbackModeStopReason int reason) {
         Rlog.d(TAG, "exit SCBM and emergency mode");
         final Phone smsPhone = mSmsPhone;
         boolean wasInScbm = isInScbm();
-        exitEmergencySmsCallbackMode();
+        exitEmergencySmsCallbackMode(reason);
 
         // The emergency mode needs to be checked to ensure that there is no ongoing emergency SMS.
         if (wasInScbm && mOngoingEmergencySmsIds.isEmpty()) {
@@ -1505,13 +1585,22 @@
 
     /**
      * Exits emergency SMS callback mode.
+     *
+     * @param reason The reason for exiting. See
+     *               {@link TelephonyManager.EmergencyCallbackModeStopReason} for possible values.
      */
-    private void exitEmergencySmsCallbackMode() {
+    private void exitEmergencySmsCallbackMode(
+            @TelephonyManager.EmergencyCallbackModeStopReason int reason) {
         // Remove pending message if present.
         mHandler.removeMessages(MSG_EXIT_SCBM);
 
         if (isInScbm()) {
             Rlog.i(TAG, "exit SCBM");
+
+            if (mFeatureFlags.emergencyCallbackModeNotification()) {
+                mSmsPhone.stopEmergencyCallbackMode(EMERGENCY_CALLBACK_MODE_SMS, reason);
+            }
+
             setIsInScbm(false);
         }
 
@@ -1588,7 +1677,7 @@
         final boolean isAirplaneModeOn = isAirplaneModeOn(mContext);
         boolean needToTurnOnRadio = !isRadioOn() || isAirplaneModeOn;
         final SatelliteController satelliteController = SatelliteController.getInstance();
-        boolean needToTurnOffSatellite = satelliteController.isSatelliteEnabled();
+        boolean needToTurnOffSatellite = satelliteController.isSatelliteEnabledOrBeingEnabled();
 
         if (isAirplaneModeOn && !isPowerOff()
                 && !phone.getServiceStateTracker().getDesiredPowerState()) {
@@ -1607,14 +1696,24 @@
 
             final Phone phoneForEmergency = phone;
             final android.telecom.Connection expectedConnection = mOngoingConnection;
-            final int waitForInServiceTimeout =
-                    needToTurnOnRadio ? DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS : 0;
+            final int waitForInServiceTimeout = needToTurnOnRadio ? mWaitForInServiceTimeoutMs : 0;
             Rlog.i(TAG, "turnOnRadioAndSwitchDds: timeout=" + waitForInServiceTimeout);
             mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() {
                 @Override
                 public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
+                    // Make sure the Call has not already been canceled by the user.
+                    if (expectedConnection.getState() == STATE_DISCONNECTED) {
+                        Rlog.i(TAG, "Call disconnected before the outgoing call was placed."
+                                + "Skipping call placement.");
+                        // If call is already canceled by the user, notify modem to exit emergency
+                        // call mode by sending radio on with forEmergencyCall=false.
+                        for (Phone phone : mPhoneFactoryProxy.getPhones()) {
+                            phone.setRadioPower(true, false, false, true);
+                        }
+                        return;
+                    }
                     if (!isRadioReady) {
-                        if (satelliteController.isSatelliteEnabled()) {
+                        if (satelliteController.isSatelliteEnabledOrBeingEnabled()) {
                             // Could not turn satellite off
                             Rlog.e(TAG, "Failed to turn off satellite modem.");
                             completeEmergencyMode(emergencyType, DisconnectCause.SATELLITE_ENABLED);
@@ -1647,7 +1746,7 @@
                         return false;
                     }
                     return phone.getServiceStateTracker().isRadioOn()
-                            && !satelliteController.isSatelliteEnabled();
+                            && !satelliteController.isSatelliteEnabledOrBeingEnabled();
                 }
 
                 @Override
@@ -1659,10 +1758,9 @@
                     }
                     // onTimeout shall be called only with the Phone for emergency
                     return phone.getServiceStateTracker().isRadioOn()
-                            && !satelliteController.isSatelliteEnabled();
+                            && !satelliteController.isSatelliteEnabledOrBeingEnabled();
                 }
-            }, !isTestEmergencyNumber, phone, isTestEmergencyNumber, waitForInServiceTimeout,
-                    /* forNormalRoutingEmergencyCall */ false);
+            }, !isTestEmergencyNumber, phone, isTestEmergencyNumber, waitForInServiceTimeout);
         } else {
             switchDdsAndSetEmergencyMode(phone, emergencyType);
         }
diff --git a/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
index 26da1c2..e2291c9 100644
--- a/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
+++ b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
@@ -85,7 +85,7 @@
      */
     public void triggerRadioOnAndListen(RadioOnStateListener.Callback callback,
             boolean forEmergencyCall, Phone phoneForEmergencyCall, boolean isTestEmergencyNumber,
-            int emergencyTimeoutIntervalMillis, boolean forNormalRoutingEmergencyCall) {
+            int emergencyTimeoutIntervalMillis) {
         setupListeners();
         mCallback = callback;
         mInProgressListeners.clear();
@@ -102,13 +102,9 @@
             mListeners.get(i).waitForRadioOn(phone, this, forEmergencyCall, forEmergencyCall
                     && phone == phoneForEmergencyCall, timeoutCallbackInterval);
         }
-        powerOnRadio(forEmergencyCall, phoneForEmergencyCall, isTestEmergencyNumber,
-                forNormalRoutingEmergencyCall);
-        if (SatelliteController.getInstance().isSatelliteEnabled()
-                || SatelliteController.getInstance().isSatelliteBeingEnabled()) {
-            // TODO: phoneForEmergencyCall is actually ignored, SatelliteController#mSatelliePhone
-            //  is being used instead.
-            powerOffSatellite(phoneForEmergencyCall);
+        powerOnRadio(forEmergencyCall, phoneForEmergencyCall, isTestEmergencyNumber);
+        if (SatelliteController.getInstance().isSatelliteEnabledOrBeingEnabled()) {
+            powerOffSatellite();
         }
     }
 
@@ -117,25 +113,17 @@
      * get an onServiceStateChanged() callback when the radio successfully comes up.
      */
     private void powerOnRadio(boolean forEmergencyCall, Phone phoneForEmergencyCall,
-            boolean isTestEmergencyNumber, boolean forNormalRoutingEmergencyCall) {
+            boolean isTestEmergencyNumber) {
 
         // Always try to turn on the radio here independent of APM setting - if we got here in the
         // first place, the radio is off independent of APM setting.
         for (Phone phone : PhoneFactory.getPhones()) {
             Rlog.d(TAG, "powerOnRadio, enabling Radio");
             if (isTestEmergencyNumber) {
-                phone.setRadioPowerOnForTestEmergencyCall(
-                        (phone == phoneForEmergencyCall) && !forNormalRoutingEmergencyCall);
+                phone.setRadioPowerOnForTestEmergencyCall(phone == phoneForEmergencyCall);
             } else {
-                if (forNormalRoutingEmergencyCall) {
-                    if (phone.getServiceStateTracker() != null) {
-                        // Clear all radio off reasons to ensure that the radio is turned on for
-                        // normal routing emergency call.
-                        phone.getServiceStateTracker().clearAllRadioOffReasons();
-                    }
-                }
-                phone.setRadioPower(true, forEmergencyCall && !forNormalRoutingEmergencyCall,
-                        (phone == phoneForEmergencyCall) && !forNormalRoutingEmergencyCall, false);
+                phone.setRadioPower(true, forEmergencyCall, phone == phoneForEmergencyCall,
+                        false);
             }
         }
 
@@ -162,7 +150,7 @@
      * Attempt to power off the satellite modem. We'll eventually get an
      * onSatelliteModemStateChanged() callback when the satellite modem is successfully disabled.
      */
-    private void powerOffSatellite(Phone phoneForEmergencyCall) {
+    private void powerOffSatellite() {
         SatelliteController satelliteController = SatelliteController.getInstance();
         satelliteController.requestSatelliteEnabled(
                 false /* enableSatellite */, false /* enableDemoMode */, false /* isEmergency */,
diff --git a/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java b/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java
index 1b1922c..f44dc70 100644
--- a/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java
+++ b/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java
@@ -157,6 +157,11 @@
         public void onRegistrationFailure(int causeCode) {
             Rlog.d(TAG, "onRegistrationFailure: causeCode " + causeCode);
         }
+
+        @Override
+        public void onTerrestrialNetworkAvailableChanged(boolean isAvailable) {
+            Rlog.d(TAG, "onTerrestrialNetworkAvailableChanged: isAvailable " + isAvailable);
+        }
     };
 
     private Callback mCallback; // The callback to notify upon completion.
@@ -230,7 +235,7 @@
         // Register for RADIO_OFF to handle cases where emergency call is dialed before
         // we receive UNSOL_RESPONSE_RADIO_STATE_CHANGED with RADIO_OFF.
         registerForRadioOff();
-        if (mSatelliteController.isSatelliteEnabled()) {
+        if (mSatelliteController.isSatelliteEnabledOrBeingEnabled()) {
             // Register for satellite modem state changed to notify when satellite is disabled.
             registerForSatelliteEnabledChanged();
         }
@@ -401,7 +406,7 @@
                 Rlog.d(TAG, "Trying (again) to turn the radio on and satellite modem off.");
                 mPhone.setRadioPower(true, mForEmergencyCall, mSelectedPhoneForEmergencyCall,
                         false);
-                if (mSatelliteController.isSatelliteEnabled()) {
+                if (mSatelliteController.isSatelliteEnabledOrBeingEnabled()) {
                     mSatelliteController.requestSatelliteEnabled(
                             false /* enableSatellite */, false /* enableDemoMode */,
                             false /* isEmergency*/,
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
index b758733..c5cc769 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
@@ -749,10 +749,12 @@
         @Override
         public boolean processMessage(Message message) {
             if (message.what == CMD_SERVICE_DISCONNECTED) {
+                EuiccSession.get().endAllSessions();
                 mEuiccService = null;
                 transitionTo(mDisconnectedState);
                 return HANDLED;
             } else if (message.what == CMD_LINGER_TIMEOUT) {
+                EuiccSession.get().endAllSessions();
                 unbind();
                 transitionTo(mAvailableState);
                 return HANDLED;
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index 715116e..cc9d793 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -22,6 +22,7 @@
 import android.Manifest.permission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
@@ -70,6 +71,7 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.euicc.EuiccConnector.OtaStatusChangedCallback;
 import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.UiccController;
@@ -1089,7 +1091,7 @@
             // system or the caller manage the target subscription, we let it continue. This is
             // because deleting subscription won't change status of any other subscriptions.
             if (!callerCanWriteEmbeddedSubscriptions
-                    && !mSubscriptionManager.canManageSubscription(sub, callingPackage)
+                    && !canManageSubscription(sub, callingPackage)
                     && !adminOwned) {
                 Log.e(TAG, "No permissions: " + subscriptionId + " adminOwned=" + adminOwned);
                 sendResult(callbackIntent, ERROR, null /* extrasIntent */);
@@ -1208,7 +1210,7 @@
                 if (callerCanWriteEmbeddedSubscriptions) {
                     passConsent = true;
                 } else {
-                    if (!mSubscriptionManager.canManageSubscription(sub, callingPackage)) {
+                    if (!canManageSubscription(sub, callingPackage)) {
                         Log.e(TAG, "Not permitted to switch to sub: " + subscriptionId);
                         sendResult(callbackIntent, ERROR, null /* extrasIntent */);
                         return;
@@ -1287,7 +1289,7 @@
             if ((cardId == TelephonyManager.UNSUPPORTED_CARD_ID || subInfo.getCardId() == cardId)
                     && subInfo.isEmbedded()
                     && (callerCanWriteEmbeddedSubscriptions
-                    || mSubscriptionManager.canManageSubscription(subInfo, callingPackage))) {
+                    || canManageSubscription(subInfo, callingPackage))) {
                 return subInfo.getPortIndex();
             }
         }
@@ -1558,7 +1560,7 @@
             // system or the caller can manage the target subscription, we let it continue. This is
             // because updating subscription nickname won't affect any other subscriptions.
             if (!callerCanWriteEmbeddedSubscriptions
-                    && !mSubscriptionManager.canManageSubscription(sub, callingPackage)) {
+                    && !canManageSubscription(sub, callingPackage)) {
                 Log.e(TAG, "No permissions: " + subscriptionId);
                 sendResult(callbackIntent, ERROR, null /* extrasIntent */);
                 return;
@@ -2067,7 +2069,7 @@
             if ((cardId == TelephonyManager.UNSUPPORTED_CARD_ID || subInfo.getCardId() == cardId)
                     && subInfo.isEmbedded()
                     && (!usePortIndex || subInfo.getPortIndex() == targetPortIndex)
-                    && mSubscriptionManager.canManageSubscription(subInfo, callingPackage)) {
+                    && canManageSubscription(subInfo, callingPackage)) {
                 return true;
             }
         }
@@ -2136,8 +2138,7 @@
                     if (subInfo.isEmbedded()
                             && subInfo.getCardId() == cardId
                             && (!usePortIndex || subInfo.getPortIndex() == targetPortIndex)
-                            && mSubscriptionManager.canManageSubscription(
-                            subInfo, callingPackage)) {
+                            && canManageSubscription(subInfo, callingPackage)) {
                         return true;
                     }
                 }
@@ -2156,7 +2157,7 @@
         } else {
             for (SubscriptionInfo subInfo : subInfoList) {
                 if (subInfo.isEmbedded()
-                        && mSubscriptionManager.canManageSubscription(subInfo, callingPackage)) {
+                        && canManageSubscription(subInfo, callingPackage)) {
                     return true;
                 }
             }
@@ -2391,4 +2392,13 @@
                     methodName + " is unsupported without " + FEATURE_TELEPHONY_EUICC);
         }
     }
+
+    private boolean canManageSubscription(SubscriptionInfo subInfo, String packageName) {
+        if (Flags.hsumPackageManager() && UserManager.isHeadlessSystemUserMode()) {
+            return mSubscriptionManager.canManageSubscriptionAsUser(subInfo, packageName,
+                    UserHandle.of(ActivityManager.getCurrentUser()));
+        } else {
+            return mSubscriptionManager.canManageSubscription(subInfo, packageName);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccSession.java b/src/java/com/android/internal/telephony/euicc/EuiccSession.java
index 99b8720..f17789f 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccSession.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccSession.java
@@ -15,6 +15,7 @@
  */
 package com.android.internal.telephony.euicc;
 
+import android.annotation.Nullable;
 import android.util.ArraySet;
 
 import com.android.internal.annotations.GuardedBy;
@@ -85,14 +86,18 @@
 
     /** Returns {@code true} if there is at least one session ongoing. */
     public boolean hasSession() {
-        boolean hasSession;
-        synchronized(this) {
-            hasSession = !mSessions.isEmpty();
-        }
+        boolean hasSession = hasSessionInternal();
         Rlog.i(TAG, "hasSession: " + hasSession);
         return hasSession;
     }
 
+    // The bare metal implementation of hasSession() without logging.
+    private boolean hasSessionInternal() {
+        synchronized(this) {
+            return !mSessions.isEmpty();
+        }
+    }
+
     /**
      * Notes that a logical channel may be opened by the {@code apduSender}, which will
      * be used to close the channel when session ends (see {@link #endSession()}).
@@ -104,7 +109,7 @@
     public void noteChannelOpen(ApduSender apduSender) {
         Rlog.i(TAG, "noteChannelOpen: " + apduSender);
         synchronized(this) {
-            if (hasSession()) {
+            if (hasSessionInternal()) {
                 mApduSenders.add(apduSender);
             }
         }
@@ -112,19 +117,41 @@
 
     /**
      * Marks the end of a eUICC transaction session. If this ends the last ongoing session,
-     * try to close the logical channel using the noted {@code apduSender}
+     * try to close the logical channel using the noted {@code apduSender}s
      * (see {@link #noteChannelOpen()}).
      *
      * @param sessionId The session ID.
      */
     public void endSession(String sessionId) {
         Rlog.i(TAG, "endSession: " + sessionId);
+        endSessionInternal(sessionId);
+    }
+
+    /**
+     * Marks the end of all eUICC transaction sessions and close the logical
+     * channels using the noted {@code apduSender}s
+     * (see {@link #noteChannelOpen()}).
+     *
+     * <p>This is useful in global cleanup e.g. when EuiccService
+     * implementation app crashes and indivial {@link #endSession()} calls
+     * won't happen in {@link EuiccConnector}.
+     */
+    public void endAllSessions() {
+        Rlog.i(TAG, "endAllSessions");
+        endSessionInternal(null);
+    }
+
+    // The implementation of endSession(sessionId) or endAllSessions() when the sessionId is null,
+    // without logging.
+    private void endSessionInternal(@Nullable String sessionId) {
         ApduSender[] apduSenders = new ApduSender[0];
         synchronized(this) {
-            boolean sessionRemoved = mSessions.remove(sessionId);
-            // sessionRemoved is false if the `sessionId` was never started or there was
-            // no session at all i.e. `sessions` is empty. Don't bother invoke `apduSender`.
-            if (sessionRemoved && mSessions.isEmpty()) {
+            boolean sessionRemoved = removeOrClear(mSessions, sessionId);
+            // 1. sessionRemoved is false if the `sessionId` was never started or there was
+            // no session. Don't bother invoke `apduSender`.
+            // 2. If some session is removed, and as a result there is no more session, we
+            // can clsoe channels.
+            if (sessionRemoved && !hasSessionInternal()) {
                 // copy mApduSenders to a local variable so we don't call closeAnyOpenChannel()
                 // which can take time in synchronized block.
                 apduSenders = mApduSenders.toArray(apduSenders);
@@ -136,6 +163,21 @@
         }
     }
 
+    /**
+     * Removes the given element from the set. If the element is null, clears the set.
+     *
+     * @return true if the set changed as a result of the call
+     */
+    private static boolean removeOrClear(Set<String> collection, @Nullable String element) {
+        if (element == null) {
+            boolean collectionChanged = !collection.isEmpty();
+            collection.clear();
+            return collectionChanged;
+        } else {
+            return collection.remove(element);
+        }
+    }
+
     @VisibleForTesting
     public EuiccSession() {}
 }
diff --git a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index e5afbeb..25a83bd 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -24,6 +24,7 @@
 import android.os.Message;
 import android.telephony.ServiceState;
 
+import com.android.internal.R;
 import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
 import com.android.internal.telephony.InboundSmsHandler;
 import com.android.internal.telephony.Phone;
@@ -165,8 +166,13 @@
                 + " SS=" + ss
                 + " " + SmsController.formatCrossStackMessageId(tracker.mMessageId));
 
+        boolean allowCheckMessageInNotConnected =
+                mContext.getResources()
+                        .getBoolean(R.bool.config_satellite_allow_check_message_in_not_connected);
+        boolean mtPollingMessageThatsAllowedInOOS =
+                tracker.isMtSmsPollingMessage(mContext) && allowCheckMessageInNotConnected;
         // if sms over IMS is not supported on data and voice is not available...
-        if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
+        if (!isIms() && ss != ServiceState.STATE_IN_SERVICE && !mtPollingMessageThatsAllowedInOOS) {
         //In 5G case only Data Rat is reported.
             if(mPhone.getServiceState().getRilDataRadioTechnology()
                     != ServiceState.RIL_RADIO_TECHNOLOGY_NR) {
diff --git a/src/java/com/android/internal/telephony/ims/ImsResolver.java b/src/java/com/android/internal/telephony/ims/ImsResolver.java
index eb389b7..b95911f 100644
--- a/src/java/com/android/internal/telephony/ims/ImsResolver.java
+++ b/src/java/com/android/internal/telephony/ims/ImsResolver.java
@@ -19,6 +19,7 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -51,6 +52,7 @@
 import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
@@ -129,6 +131,10 @@
     private static final int HANDLER_MSIM_CONFIGURATION_CHANGE = 7;
     // clear any carrier ImsService test overrides.
     private static final int HANDLER_CLEAR_CARRIER_IMS_SERVICE_CONFIG = 8;
+    // the user has switched
+    private static final int HANDLER_USER_SWITCHED = 9;
+    // A dynamic query has failed permanently, remove the package from being tracked
+    private static final int HANDLER_REMOVE_PACKAGE_PERM_ERROR = 10;
 
     // Delay between dynamic ImsService queries.
     private static final int DELAY_DYNAMIC_QUERY_MS = 5000;
@@ -158,14 +164,19 @@
     }
 
     private static class OverrideConfig {
+        public final String packageName;
         public final int slotId;
+        public final int userId;
         public final boolean isCarrierService;
-        public final Map<Integer, String> featureTypeToPackageMap;
+        public final int[] featureTypes;
 
-        OverrideConfig(int slotIndex, boolean isCarrier, Map<Integer, String> feature) {
+        OverrideConfig(String pkgName, int slotIndex, int userIndex, boolean isCarrier,
+                int[] features) {
+            packageName = pkgName;
             slotId = slotIndex;
+            userId = userIndex;
             isCarrierService = isCarrier;
-            featureTypeToPackageMap = feature;
+            featureTypes = features;
         }
     }
 
@@ -175,7 +186,8 @@
      */
     @VisibleForTesting
     public static class ImsServiceInfo {
-        public ComponentName name;
+        public final ComponentName name;
+        public final Set<UserHandle> users = new HashSet<>();
         // Determines if features were created from metadata in the manifest or through dynamic
         // query.
         public boolean featureFromMetadata = true;
@@ -184,7 +196,8 @@
         // Map slotId->Feature
         private final HashSet<ImsFeatureConfiguration.FeatureSlotPair> mSupportedFeatures;
 
-        public ImsServiceInfo() {
+        public ImsServiceInfo(ComponentName componentName) {
+            name = componentName;
             mSupportedFeatures = new HashSet<>();
         }
 
@@ -208,37 +221,41 @@
         public boolean equals(Object o) {
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
-
             ImsServiceInfo that = (ImsServiceInfo) o;
-
-            if (name != null ? !name.equals(that.name) : that.name != null) return false;
-            if (!mSupportedFeatures.equals(that.mSupportedFeatures)) {
-                return false;
-            }
-            return controllerFactory != null ? controllerFactory.equals(that.controllerFactory)
-                    : that.controllerFactory == null;
+            return Objects.equals(name, that.name) && Objects.equals(users, that.users);
         }
 
         @Override
         public int hashCode() {
-            // We do not include mSupportedFeatures in hashcode because the internal structure
-            // changes after adding.
-            int result = name != null ? name.hashCode() : 0;
-            result = 31 * result + (controllerFactory != null ? controllerFactory.hashCode() : 0);
-            return result;
+            return Objects.hash(name, users);
         }
 
         @Override
         public String toString() {
             return "[ImsServiceInfo] name="
                     + name
-                    + ", featureFromMetadata="
+                    + ", user="
+                    + users
+                    + ",featureFromMetadata="
                     + featureFromMetadata
                     + ","
                     + printFeatures(mSupportedFeatures);
         }
     }
 
+    private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            final UserHandle handle = intent.getParcelableExtra(Intent.EXTRA_USER,
+                    UserHandle.class);
+            switch (action) {
+                case Intent.ACTION_USER_SWITCHED ->
+                        mHandler.obtainMessage(HANDLER_USER_SWITCHED, handle).sendToTarget();
+            }
+        }
+    };
+
     // Receives broadcasts from the system involving changes to the installed applications. If
     // an ImsService that we are configured to use is installed, we must bind to it.
     private final BroadcastReceiver mAppChangedReceiver = new BroadcastReceiver() {
@@ -311,6 +328,17 @@
     };
 
     /**
+     * Testing interface used to mock ActivityManager in testing
+     */
+    @VisibleForTesting
+    public interface ActivityManagerProxy {
+        /**
+         * @return The current user
+         */
+        UserHandle getCurrentUser();
+    }
+
+    /**
      * Testing interface used to mock SubscriptionManager in testing
      */
     @VisibleForTesting
@@ -360,6 +388,13 @@
         }
     };
 
+    private ActivityManagerProxy mActivityManagerProxy = new ActivityManagerProxy() {
+        @Override
+        public UserHandle getCurrentUser() {
+            return UserHandle.of(ActivityManager.getCurrentUser());
+        }
+    };
+
     /**
      * Testing interface for injecting mock ImsServiceControllers.
      */
@@ -470,6 +505,11 @@
                     maybeRemovedImsService(packageName);
                     break;
                 }
+                case HANDLER_REMOVE_PACKAGE_PERM_ERROR: {
+                    Pair<String, UserHandle> packageInfo = (Pair<String, UserHandle>) msg.obj;
+                    maybeRemovedImsServiceForUser(packageInfo.first, packageInfo.second);
+                    break;
+                }
                 case HANDLER_BOOT_COMPLETE: {
                     if (!mBootCompletedHandlerRan) {
                         mBootCompletedHandlerRan = true;
@@ -517,11 +557,15 @@
                 }
                 case HANDLER_OVERRIDE_IMS_SERVICE_CONFIG: {
                     OverrideConfig config = (OverrideConfig) msg.obj;
+                    setPackageNameUserOverride(config.packageName, config.userId);
+                    Map<Integer, String> featureConfig = new HashMap<>();
+                    for (int featureType : config.featureTypes) {
+                        featureConfig.put(featureType, config.packageName);
+                    }
                     if (config.isCarrierService) {
-                        overrideCarrierService(config.slotId,
-                                config.featureTypeToPackageMap);
+                        overrideCarrierService(config.slotId, featureConfig);
                     } else {
-                        overrideDeviceService(config.featureTypeToPackageMap);
+                        overrideDeviceService(featureConfig);
                     }
                     break;
                 }
@@ -534,6 +578,11 @@
                     clearCarrierServiceOverrides(msg.arg1);
                     break;
                 }
+                case HANDLER_USER_SWITCHED: {
+                    UserHandle handle = (UserHandle) msg.obj;
+                    Log.i(TAG, "onUserSwitched=" + handle);
+                    maybeAddedImsService(null);
+                }
                 default:
                     break;
             }
@@ -561,11 +610,16 @@
                 }
 
                 @Override
-                public void onPermanentError(ComponentName name) {
+                public void onPermanentError(ComponentName name, UserHandle user) {
                     Log.w(TAG, "onPermanentError: component=" + name);
                     mEventLog.log("onPermanentError - error for " + name);
-                    mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE,
-                            name.getPackageName()).sendToTarget();
+                    if (!mFeatureFlags.imsResolverUserAware()) {
+                        mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE,
+                                name.getPackageName()).sendToTarget();
+                    } else {
+                        mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE_PERM_ERROR,
+                                new Pair<>(name.getPackageName(), user)).sendToTarget();
+                    }
                 }
             };
 
@@ -573,6 +627,8 @@
     // Array index corresponds to slot, per slot there is a feature->package name mapping.
     // should only be accessed from handler
     private final SparseArray<SparseArray<String>> mOverrideServices;
+    //Used during testing, restricts the ImsService to be bound on a specific user.
+    private final Map<String, UserHandle> mImsServiceTestUserRestrictions = new HashMap<>();
     // Outer array index corresponds to Slot Id, Maps ImsFeature.FEATURE->bound ImsServiceController
     // Locked on mBoundServicesLock
     private final SparseArray<SparseArray<ImsServiceController>> mBoundImsServicesByFeature;
@@ -628,6 +684,11 @@
     }
 
     @VisibleForTesting
+    public void setActivityManagerProxy(ActivityManagerProxy proxy) {
+        mActivityManagerProxy = proxy;
+    }
+
+    @VisibleForTesting
     public Handler getHandler() {
         return mHandler;
     }
@@ -660,6 +721,10 @@
         appChangedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
         appChangedFilter.addDataScheme("package");
         mReceiverContext.registerReceiver(mAppChangedReceiver, appChangedFilter);
+        if (mFeatureFlags.imsResolverUserAware()) {
+            mReceiverContext.registerReceiver(mUserReceiver, new IntentFilter(
+                    Intent.ACTION_USER_SWITCHED));
+        }
         mReceiverContext.registerReceiver(mConfigChangedReceiver, new IntentFilter(
                 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
 
@@ -706,19 +771,24 @@
     // carrier config changed.
     private void bindCarrierServicesIfAvailable() {
         boolean hasConfigChanged = false;
+        boolean pendingDynamicQuery = false;
         for (int slotId = 0; slotId < mNumSlots; slotId++) {
             int subId = mSubscriptionManagerProxy.getSubId(slotId);
             Map<Integer, String> featureMap = getImsPackageOverrideConfig(subId);
             for (int f = ImsFeature.FEATURE_EMERGENCY_MMTEL; f < ImsFeature.FEATURE_MAX; f++) {
                 String newPackageName = featureMap.getOrDefault(f, "");
                 if (!TextUtils.isEmpty(newPackageName)) {
+                    Log.d(TAG, "bindCarrierServicesIfAvailable - carrier package found: "
+                            + newPackageName + ", feature "
+                            + ImsFeature.FEATURE_LOG_MAP.getOrDefault(f, "invalid")
+                            + " on slot " + slotId);
                     mEventLog.log("bindCarrierServicesIfAvailable - carrier package found: "
                             + newPackageName + " on slot " + slotId);
                     // Carrier configs are already available, so mark received.
                     mCarrierConfigReceived = true;
                     setSubId(slotId, subId);
                     setCarrierConfiguredPackageName(newPackageName, slotId, f);
-                    ImsServiceInfo info = getImsServiceInfoFromCache(newPackageName);
+                    ImsServiceInfo info = getVisibleImsServiceInfoFromCache(newPackageName);
                     // We do not want to trigger feature configuration changes unless there is
                     // already a valid carrier config change.
                     if (info != null && info.featureFromMetadata) {
@@ -726,11 +796,18 @@
                     } else {
                         // Config will change when this query completes
                         scheduleQueryForFeatures(info);
+                        if (info != null) pendingDynamicQuery = true;
                     }
                 }
             }
         }
-        if (hasConfigChanged) calculateFeatureConfigurationChange();
+        // we want to make sure that we are either pending to bind to a carrier configured service
+        // or bind to the device config if we potentially missed the carrier config changed
+        // indication.
+        if (hasConfigChanged || (mFeatureFlags.imsResolverUserAware()
+                && mCarrierConfigReceived && !pendingDynamicQuery)) {
+            calculateFeatureConfigurationChange();
+        }
     }
 
     /**
@@ -833,14 +910,15 @@
     }
 
     // Used for testing only.
-    public boolean overrideImsServiceConfiguration(int slotId, boolean isCarrierService,
-            Map<Integer, String> featureConfig) {
+    public boolean overrideImsServiceConfiguration(String packageName, int slotId, int userId,
+            boolean isCarrierService, int[] overrideFeatureTypes) {
         if (slotId < 0 || slotId >= mNumSlots) {
             Log.w(TAG, "overrideImsServiceConfiguration: invalid slotId!");
             return false;
         }
 
-        OverrideConfig overrideConfig = new OverrideConfig(slotId, isCarrierService, featureConfig);
+        OverrideConfig overrideConfig = new OverrideConfig(packageName, slotId, userId,
+                isCarrierService, overrideFeatureTypes);
         Message.obtain(mHandler, HANDLER_OVERRIDE_IMS_SERVICE_CONFIG, overrideConfig)
                 .sendToTarget();
         return true;
@@ -881,10 +959,14 @@
     }
 
     // not synchronized, access in handler ONLY.
-    private void removeOverridePackageName(int slotId) {
+    private Set<String> removeOverridePackageName(int slotId) {
+        Set<String> removedOverrides = new HashSet<>();
         for (int f = ImsFeature.FEATURE_EMERGENCY_MMTEL; f < ImsFeature.FEATURE_MAX; f++) {
-            getOverridePackageName(slotId).remove(f);
+            SparseArray<String> overrides = getOverridePackageName(slotId);
+            String packageName = overrides.removeReturnOld(f);
+            if (packageName != null) removedOverrides.add(packageName);
         }
+        return removedOverrides;
     }
 
     // not synchronized, access in handler ONLY.
@@ -894,6 +976,22 @@
     }
 
     // not synchronized, access in handler ONLY.
+    private void setPackageNameUserOverride(String packageName, int userId) {
+        if (packageName == null || packageName.isEmpty() || userId == UserHandle.USER_NULL) return;
+        Log.i(TAG, "setPackageNameUserOverride: set for " + packageName + ", user= " + userId);
+        mImsServiceTestUserRestrictions.put(packageName, UserHandle.of(userId));
+    }
+
+    // not synchronized, access in handler ONLY.
+    private void clearPackageNameUserOverride(String packageName) {
+        UserHandle handle = mImsServiceTestUserRestrictions.remove(packageName);
+        if (handle != null) {
+            Log.i(TAG, "clearPackageNameUserOverride: cleared for " + packageName
+                    + "on user " + handle);
+        }
+    }
+
+    // not synchronized, access in handler ONLY.
     private @Nullable String getOverridePackageName(int slotId,
             @ImsFeature.FeatureType int featureType) {
         return getOverridePackageName(slotId).get(featureType);
@@ -927,13 +1025,14 @@
 
     /**
      * Check the cached ImsServices that exist on this device to determine if there is a ImsService
-     * with the same package name that matches the provided configuration.
+     * with the same package name that matches the provided configuration and is configured to run
+     * in one of the active users.
      */
     // not synchronized, access in handler ONLY.
     private boolean doesCachedImsServiceExist(String packageName, int slotId,
             @ImsFeature.FeatureType int featureType) {
         // Config exists, but the carrier ImsService also needs to support this feature
-        ImsServiceInfo info = getImsServiceInfoFromCache(packageName);
+        ImsServiceInfo info = getVisibleImsServiceInfoFromCache(packageName);
         return info != null && info.getSupportedFeatures().stream().anyMatch(
                 feature -> feature.slotId == slotId && feature.featureType == featureType);
     }
@@ -1073,7 +1172,8 @@
         return null;
     }
 
-    private void putImsController(int slotId, int feature, ImsServiceController controller) {
+    private void putImsController(int slotId, int subId, int feature,
+            ImsServiceController controller) {
         if (slotId < 0 || slotId >= mNumSlots || feature <= ImsFeature.FEATURE_INVALID
                 || feature >= ImsFeature.FEATURE_MAX) {
             Log.w(TAG, "putImsController received invalid parameters - slot: " + slotId
@@ -1088,9 +1188,9 @@
             }
             mEventLog.log("putImsController - [" + slotId + ", "
                     + ImsFeature.FEATURE_LOG_MAP.get(feature) + "] -> " + controller);
-            Log.i(TAG, "ImsServiceController added on slot: " + slotId + " with feature: "
-                    + ImsFeature.FEATURE_LOG_MAP.get(feature) + " using package: "
-                    + controller.getComponentName());
+            Log.i(TAG, "ImsServiceController added on slot: " + slotId + ", subId: " + subId
+                    + " with feature: " + ImsFeature.FEATURE_LOG_MAP.get(feature)
+                    + " using package: " + controller.getComponentName());
             services.put(feature, controller);
         }
     }
@@ -1134,6 +1234,10 @@
             // features. Will only be one (if it exists), since it is a set.
             ImsServiceInfo match = getInfoByComponentName(mInstalledServicesCache, info.name);
             if (match != null) {
+                if (mFeatureFlags.imsResolverUserAware()) {
+                    match.users.clear();
+                    match.users.addAll(info.users);
+                }
                 // for dynamic query the new "info" will have no supported features yet. Don't wipe
                 // out the cache for the existing features or update yet. Instead start a query
                 // for features dynamically.
@@ -1141,9 +1245,8 @@
                     mEventLog.log("maybeAddedImsService - updating features for " + info.name
                             + ": " + printFeatures(match.getSupportedFeatures()) + " -> "
                             + printFeatures(info.getSupportedFeatures()));
-                    Log.i(TAG, "Updating features in cached ImsService: " + info.name);
-                    Log.d(TAG, "Updating features - Old features: " + match + " new features: "
-                            + info);
+                    Log.d(TAG, "Updating features in cached ImsService: " + info.name
+                            + ", old features: " + match + " new features: " + info);
                     // update features in the cache
                     match.replaceFeatures(info.getSupportedFeatures());
                     requiresCalculation = true;
@@ -1168,10 +1271,9 @@
         if (requiresCalculation) calculateFeatureConfigurationChange();
     }
 
-    // Remove the ImsService from the cache. This may have been due to the ImsService being removed
-    // from the device or was returning permanent errors when bound.
+    // Remove the ImsService from the cache due to the ImsService package being removed.
     // Called from the handler ONLY
-    private boolean maybeRemovedImsService(String packageName) {
+    private boolean maybeRemovedImsServiceOld(String packageName) {
         ImsServiceInfo match = getInfoByPackageName(mInstalledServicesCache, packageName);
         if (match != null) {
             mInstalledServicesCache.remove(match.name);
@@ -1184,6 +1286,70 @@
         return false;
     }
 
+    // Remove the ImsService from the cache due to the ImsService package being removed.
+    // Called from the handler ONLY
+    private boolean maybeRemovedImsService(String packageName) {
+        if (!mFeatureFlags.imsResolverUserAware()) {
+            return maybeRemovedImsServiceOld(packageName);
+        }
+        ImsServiceInfo match = getInfoByPackageName(mInstalledServicesCache, packageName);
+        if (match != null) {
+            List<ImsServiceInfo> imsServices = searchForImsServices(packageName,
+                    match.controllerFactory);
+            ImsServiceInfo newMatch = imsServices.isEmpty() ? null : imsServices.getFirst();
+            if (newMatch == null) {
+                clearPackageNameUserOverride(match.name.getPackageName());
+                // The package doesn't exist anymore on any user, so remove
+                mInstalledServicesCache.remove(match.name);
+                mEventLog.log("maybeRemovedImsService - removing ImsService: " + match);
+                Log.i(TAG, "maybeRemovedImsService Removing ImsService for all users: "
+                        + match.name);
+                unbindImsService(match);
+            } else {
+                // The Package exists on some users still, so modify the users
+                match.users.clear();
+                match.users.addAll(newMatch.users);
+                mEventLog.log("maybeRemovedImsService - modifying ImsService users: " + match);
+                Log.i(TAG, "maybeRemovedImsService - Modifying ImsService users " + match);
+                // If this package still remains on some users, then it is possible we are unbinding
+                // an active ImsService, but the assumption here is that the package is being
+                // removed on an active user. Be safe and unbind now - we will rebind below if
+                // needed.
+                unbindImsService(match);
+            }
+            calculateFeatureConfigurationChange();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Remove the cached ImsService for a specific user. If there are no more users available after
+     * removing the specified user, remove the ImsService cache entry entirely.
+     */
+    // Called from the handler ONLY
+    private boolean maybeRemovedImsServiceForUser(String packageName, UserHandle user) {
+        ImsServiceInfo match = getInfoByPackageName(mInstalledServicesCache, packageName);
+        if (match != null) {
+            mEventLog.log("maybeRemovedImsServiceForUser - removing ImsService " + match
+                    + "for user " + user);
+            Log.i(TAG, "maybeRemovedImsServiceForUser: Removing ImsService "
+                    + match + "for user " + user);
+            unbindImsService(match);
+            match.users.remove(user);
+            if (match.users.isEmpty()) {
+                mEventLog.log("maybeRemovedImsServiceForUser - no more users, removing "
+                        + "ImsService " + match);
+                Log.i(TAG, "maybeRemovedImsServiceForUser - no more users, removing "
+                        + "ImsService " + match);
+                mInstalledServicesCache.remove(match.name);
+            }
+            calculateFeatureConfigurationChange();
+            return true;
+        }
+        return false;
+    }
+
     private boolean isDeviceService(ImsServiceInfo info) {
         if (info == null) return false;
         synchronized (mDeviceServices) {
@@ -1193,6 +1359,14 @@
 
     private List<Integer> getSlotsForActiveCarrierService(ImsServiceInfo info) {
         if (info == null) return Collections.emptyList();
+        if (mFeatureFlags.imsResolverUserAware()) {
+            UserHandle activeUser = getUserForBind(info);
+            if (activeUser == null) {
+                Log.d(TAG, "getSlotsForActiveCarrierService: ImsService " + info.name + "is not "
+                        + "configured to run for any users, skipping...");
+                return Collections.emptyList();
+            }
+        }
         List<Integer> slots = new ArrayList<>(mNumSlots);
         for (int i = 0; i < mNumSlots; i++) {
             if (!TextUtils.isEmpty(getCarrierConfiguredPackageNames(i).values().stream()
@@ -1222,7 +1396,7 @@
         return searchMap.get(matchValue);
     }
 
-    private void bindImsServiceWithFeatures(ImsServiceInfo info,
+    private void bindImsServiceWithFeatures(ImsServiceInfo info, UserHandle user,
             Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
         // Only bind if there are features that will be created by the service.
         if (shouldFeaturesCauseBind(features)) {
@@ -1230,10 +1404,21 @@
             ImsServiceController controller = getControllerByServiceInfo(mActiveControllers, info);
             SparseIntArray slotIdToSubIdMap = mSlotIdToSubIdMap.clone();
             if (controller != null) {
-                Log.i(TAG, "ImsService connection exists for " + info.name + ", updating features "
-                        + features);
                 try {
-                    controller.changeImsServiceFeatures(features, slotIdToSubIdMap);
+                    if (!mFeatureFlags.imsResolverUserAware()
+                            || Objects.equals(user, controller.getBoundUser())) {
+                        Log.i(TAG, "ImsService connection exists for " + info.name
+                                + ", updating features " + features);
+                        controller.changeImsServiceFeatures(features, slotIdToSubIdMap);
+                    } else {
+                        // Changing a user is a pretty rare event, we need to unbind and rebind
+                        // on the correct new user.
+                        Log.i(TAG, "ImsService user changed for " + info.name
+                                + ", rebinding on user " + user + ", features " + features);
+                        controller.unbind();
+                        controller.bind(user, features, slotIdToSubIdMap);
+                    }
+
                     // Features have been set, there was an error adding/removing. When the
                     // controller recovers, it will add/remove again.
                 } catch (RemoteException e) {
@@ -1243,8 +1428,9 @@
                 controller = info.controllerFactory.create(mContext, info.name, this, mRepo,
                         mFeatureFlags);
                 Log.i(TAG, "Binding ImsService: " + controller.getComponentName()
-                        + " with features: " + features);
-                controller.bind(features, slotIdToSubIdMap);
+                        + "on user " + user + " with features: " + features + ", subIdMap: "
+                        + slotIdToSubIdMap);
+                controller.bind(user, features, slotIdToSubIdMap);
                 mEventLog.log("bindImsServiceWithFeatures - create new controller: "
                         + controller);
             }
@@ -1285,7 +1471,7 @@
             imsFeaturesBySlot.addAll(info.getSupportedFeatures().stream()
                     .filter(feature -> info.name.getPackageName().equals(
                             getCarrierConfiguredPackageName(feature.slotId, feature.featureType)))
-                    .collect(Collectors.toList()));
+                    .toList());
             return imsFeaturesBySlot;
         }
         if (isDeviceService(info)) {
@@ -1298,7 +1484,7 @@
                     // by the carrier ImsService.
                     .filter(feature -> !doesCarrierConfigurationExist(feature.slotId,
                             feature.featureType))
-                    .collect(Collectors.toList()));
+                    .toList());
         }
         return imsFeaturesBySlot;
     }
@@ -1309,8 +1495,9 @@
      * adds the ImsServiceController from the mBoundImsServicesByFeature structure.
      */
     @Override
-    public void imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller) {
-        putImsController(slotId, feature, controller);
+    public void imsServiceFeatureCreated(int slotId, int subId, int feature,
+            ImsServiceController controller) {
+        putImsController(slotId, subId, feature, controller);
     }
 
     /**
@@ -1341,13 +1528,19 @@
     }
 
     @Override
-    public void imsServiceBindPermanentError(ComponentName name) {
+    public void imsServiceBindPermanentError(ComponentName name, UserHandle user) {
         if (name == null) {
             return;
         }
-        Log.w(TAG, "imsServiceBindPermanentError: component=" + name);
-        mEventLog.log("imsServiceBindPermanentError - for " + name);
-        mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE, name.getPackageName()).sendToTarget();
+        Log.w(TAG, "imsServiceBindPermanentError: component=" + name + ", user=" + user);
+        mEventLog.log("imsServiceBindPermanentError - for " + name + ", user " + user);
+        if (!mFeatureFlags.imsResolverUserAware()) {
+            mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE,
+                    name.getPackageName()).sendToTarget();
+        } else {
+            mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE_PERM_ERROR,
+                    new Pair<>(name.getPackageName(), user)).sendToTarget();
+        }
     }
 
     /**
@@ -1381,7 +1574,10 @@
     private void clearCarrierServiceOverrides(int slotId) {
         Log.i(TAG, "clearing carrier ImsService overrides");
         mEventLog.log("clearing carrier ImsService overrides");
-        removeOverridePackageName(slotId);
+        Set<String> removedPackages = removeOverridePackageName(slotId);
+        for (String pkg : removedPackages) {
+            clearPackageNameUserOverride(pkg);
+        }
         carrierConfigChanged(slotId, getSubId(slotId));
     }
 
@@ -1399,8 +1595,9 @@
                         + oldPackageName + " -> " + overridePackageName);
                 mEventLog.log("overrideDeviceService - device package changed (override): "
                         + oldPackageName + " -> " + overridePackageName);
+                clearPackageNameUserOverride(oldPackageName);
                 setDeviceConfiguration(overridePackageName, featureType);
-                ImsServiceInfo info = getImsServiceInfoFromCache(overridePackageName);
+                ImsServiceInfo info = getVisibleImsServiceInfoFromCache(overridePackageName);
                 if (info == null || info.featureFromMetadata) {
                     requiresRecalc = true;
                 } else {
@@ -1430,7 +1627,7 @@
         ArrayMap<String, ImsServiceInfo> featureDynamicImsPackages = new ArrayMap<>();
         for (int f = ImsFeature.FEATURE_EMERGENCY_MMTEL; f < ImsFeature.FEATURE_MAX; f++) {
             String packageName = getDeviceConfiguration(f);
-            ImsServiceInfo serviceInfo = getImsServiceInfoFromCache(packageName);
+            ImsServiceInfo serviceInfo = getVisibleImsServiceInfoFromCache(packageName);
             if (serviceInfo != null && !serviceInfo.featureFromMetadata
                     && !featureDynamicImsPackages.containsKey(packageName)) {
                 featureDynamicImsPackages.put(packageName, serviceInfo);
@@ -1465,13 +1662,7 @@
             setCarrierConfiguredPackageName(newPackageName, slotId, f);
             // Carrier config may have not changed, but we still want to kick off a recalculation
             // in case there has been a change to the supported device features.
-            ImsServiceInfo info = getImsServiceInfoFromCache(newPackageName);
-            Log.i(TAG, "updateBoundServices - carrier package changed: "
-                    + oldPackageName + " -> " + newPackageName + " on slot " + slotId
-                    + ", hasConfigChanged=" + hasConfigChanged);
-            mEventLog.log("updateBoundServices - carrier package changed: "
-                    + oldPackageName + " -> " + newPackageName + " on slot " + slotId
-                    + ", hasConfigChanged=" + hasConfigChanged);
+            ImsServiceInfo info = getVisibleImsServiceInfoFromCache(newPackageName);
             if (info == null || info.featureFromMetadata) {
                 hasConfigChanged = true;
             } else {
@@ -1479,6 +1670,12 @@
                 scheduleQueryForFeatures(info);
                 didQuerySchedule = true;
             }
+            Log.i(TAG, "updateBoundServices - carrier package changed: "
+                    + oldPackageName + " -> " + newPackageName + " on slot " + slotId
+                    + ", hasConfigChanged=" + hasConfigChanged);
+            mEventLog.log("updateBoundServices - carrier package changed: "
+                    + oldPackageName + " -> " + newPackageName + " on slot " + slotId
+                    + ", hasConfigChanged=" + hasConfigChanged);
         }
         if (hasConfigChanged) calculateFeatureConfigurationChange();
 
@@ -1530,7 +1727,7 @@
     }
 
     private void scheduleQueryForFeatures(ComponentName name, int delayMs) {
-        ImsServiceInfo service = getImsServiceInfoFromCache(name.getPackageName());
+        ImsServiceInfo service = getVisibleImsServiceInfoFromCache(name.getPackageName());
         if (service == null) {
             Log.w(TAG, "scheduleQueryForFeatures: Couldn't find cached info for name: " + name);
             return;
@@ -1614,6 +1811,12 @@
 
     // Starts a dynamic query. Called from handler ONLY.
     private void startDynamicQuery(ImsServiceInfo service) {
+        UserHandle user = getUserForBind(service);
+        if (user == null) {
+            Log.i(TAG, "scheduleQueryForFeatures: skipping query for ImsService that is not"
+                    + " running: " + service);
+            return;
+        }
         // if not current device/carrier service, don't perform query. If this changes, this method
         // will be called again.
         if (!isDeviceService(service) && getSlotsForActiveCarrierService(service).isEmpty()) {
@@ -1622,7 +1825,7 @@
             return;
         }
         mEventLog.log("startDynamicQuery - starting query for " + service);
-        boolean queryStarted = mFeatureQueryManager.startQuery(service.name,
+        boolean queryStarted = mFeatureQueryManager.startQuery(service.name, user,
                 service.controllerFactory.getServiceInterface());
         if (!queryStarted) {
             Log.w(TAG, "startDynamicQuery: service could not connect. Retrying after delay.");
@@ -1637,7 +1840,7 @@
     // process complete dynamic query. Called from handler ONLY.
     private void dynamicQueryComplete(ComponentName name,
             Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
-        ImsServiceInfo service = getImsServiceInfoFromCache(name.getPackageName());
+        ImsServiceInfo service = getVisibleImsServiceInfoFromCache(name.getPackageName());
         if (service == null) {
             Log.w(TAG, "dynamicQueryComplete: Couldn't find cached info for name: "
                     + name);
@@ -1683,17 +1886,92 @@
 
     // Calculate the new configuration for the bound ImsServices.
     // Should ONLY be called from the handler.
-    private void calculateFeatureConfigurationChange() {
+    private void calculateFeatureConfigurationChangeOld() {
         for (ImsServiceInfo info : mInstalledServicesCache.values()) {
             Set<ImsFeatureConfiguration.FeatureSlotPair> features = calculateFeaturesToCreate(info);
             if (shouldFeaturesCauseBind(features)) {
-                bindImsServiceWithFeatures(info, features);
+                bindImsServiceWithFeatures(info, mContext.getUser(), features);
             } else {
                 unbindImsService(info);
             }
         }
     }
 
+    // Should ONLY be called from the handler.
+    private void calculateFeatureConfigurationChange() {
+        if (!mFeatureFlags.imsResolverUserAware()) {
+            calculateFeatureConfigurationChangeOld();
+            return;
+        }
+        // There is an implicit assumption here that the ImsServiceController will remove itself
+        // from caches BEFORE adding a new one. If this assumption is broken, we will remove a valid
+        // ImsServiceController from the cache accidentally. To keep this assumption valid, we will
+        // iterate through the cache twice - first to unbind, then to bind and change features of
+        // existing ImsServiceControllers. This is a little inefficient, but there should be on the
+        // order of 10 installed ImsServices at most, so running through this list twice is
+        // reasonable vs the memory cost of caching binding vs unbinding services.
+
+        // Unbind first if needed
+        for (ImsServiceInfo info : mInstalledServicesCache.values()) {
+            Set<ImsFeatureConfiguration.FeatureSlotPair> features = calculateFeaturesToCreate(info);
+            UserHandle user = getUserForBind(info);
+            if (shouldFeaturesCauseBind(features) && user != null) continue;
+            unbindImsService(info);
+        }
+        // Bind/alter features second
+        for (ImsServiceInfo info : mInstalledServicesCache.values()) {
+            Set<ImsFeatureConfiguration.FeatureSlotPair> features = calculateFeaturesToCreate(info);
+            UserHandle user = getUserForBind(info);
+            if (shouldFeaturesCauseBind(features) && user != null) {
+                bindImsServiceWithFeatures(info, user, features);
+            }
+        }
+    }
+
+    /**
+     * Returns the UserHandle that should be used to bind the ImsService.
+     *
+     * @return The UserHandle of the user that telephony is running in if the
+     * ImsService is configured to run in that user, or the current active user
+     * if not. Returns null if the ImsService is not configured to run in any
+     * active user.
+     */
+    private UserHandle getUserForBind(ImsServiceInfo info) {
+        if (!mFeatureFlags.imsResolverUserAware()) {
+            return mContext.getUser();
+        }
+        UserHandle currentUser = mActivityManagerProxy.getCurrentUser();
+        List<UserHandle> activeUsers = getActiveUsers().stream()
+                .filter(info.users::contains).toList();
+        if (activeUsers.isEmpty()) return null;
+        // If there is a test restriction in place for this package, prioritize that restriction
+        UserHandle testRestriction = mImsServiceTestUserRestrictions.getOrDefault(
+                info.name.getPackageName(), null);
+        if (testRestriction != null && activeUsers.stream()
+                .anyMatch(u -> Objects.equals(u, testRestriction))) {
+            return testRestriction;
+        }
+        // Prioritize the User that Telephony is in, since it is always running
+        if (activeUsers.stream()
+                .anyMatch(u -> Objects.equals(u, mContext.getUser()))) {
+            return mContext.getUser();
+        }
+        if (activeUsers.stream().anyMatch(u -> Objects.equals(u, currentUser))) {
+            return currentUser;
+        }
+        return null;
+    }
+
+  /**
+   * Returns the set of full users that are currently active.
+   */
+    private Set<UserHandle> getActiveUsers() {
+        Set<UserHandle> profiles = new HashSet<>();
+        profiles.add(mContext.getUser());
+        profiles.add(mActivityManagerProxy.getCurrentUser());
+        return profiles;
+    }
+
     private static String printFeatures(Set<ImsFeatureConfiguration.FeatureSlotPair> features) {
         StringBuilder featureString = new StringBuilder();
         featureString.append(" features: [");
@@ -1711,8 +1989,24 @@
     }
 
     /**
-     * Returns the ImsServiceInfo that matches the provided packageName. Visible for testing
-     * the ImsService caching functionality.
+     * Returns the ImsServiceInfo that matches the provided packageName if it belongs to a
+     * package that is visible as part of the set of active users.
+     */
+    public ImsServiceInfo getVisibleImsServiceInfoFromCache(String packageName) {
+        ImsServiceInfo match = getImsServiceInfoFromCache(packageName);
+        if (!mFeatureFlags.imsResolverUserAware()) {
+            return match;
+        }
+        if (match == null) return null;
+        UserHandle targetUser = getUserForBind(match);
+        Log.d(TAG, "getVisibleImsServiceInfoFromCache: " + packageName + ", match=" + match
+                + ", targetUser=" + targetUser);
+        if (targetUser != null) return match; else return null;
+    }
+
+    /**
+     * Returns the ImsServiceInfo that matches the provided packageName. This includes
+     * ImsServiceInfos that are not currently visible for the active users.
      */
     @VisibleForTesting
     public ImsServiceInfo getImsServiceInfoFromCache(String packageName) {
@@ -1738,6 +2032,12 @@
         return infos;
     }
 
+    private ImsServiceInfo getInfoFromCache(List<ImsServiceInfo> infos,
+            ComponentName componentName) {
+        return infos.stream().filter(info -> Objects.equals(info.name, componentName)).findFirst()
+                .orElse(null);
+    }
+
     private List<ImsServiceInfo> searchForImsServices(String packageName,
             ImsServiceControllerFactory controllerFactory) {
         List<ImsServiceInfo> infos = new ArrayList<>();
@@ -1745,62 +2045,84 @@
         Intent serviceIntent = new Intent(controllerFactory.getServiceInterface());
         serviceIntent.setPackage(packageName);
 
+        Set<UserHandle> profiles;
+        if (mFeatureFlags.imsResolverUserAware()) {
+            profiles = getActiveUsers();
+        } else {
+            profiles = Collections.singleton(mContext.getUser());
+        }
+        Log.v(TAG, "searchForImsServices: package=" + packageName + ", users=" + profiles);
+
         PackageManager packageManager = mContext.getPackageManager();
-        for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
-                serviceIntent,
-                PackageManager.GET_META_DATA,
-                UserHandle.of(UserHandle.myUserId()))) {
-            ServiceInfo serviceInfo = entry.serviceInfo;
+        for (UserHandle handle : profiles) {
+            for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(serviceIntent,
+                    PackageManager.GET_META_DATA, handle)) {
+                ServiceInfo serviceInfo = entry.serviceInfo;
 
-            if (serviceInfo != null) {
-                ImsServiceInfo info = new ImsServiceInfo();
-                info.name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
-                info.controllerFactory = controllerFactory;
+                if (serviceInfo != null) {
+                    ComponentName name = new ComponentName(serviceInfo.packageName,
+                            serviceInfo.name);
+                    ImsServiceInfo info = getInfoFromCache(infos, name);
+                    if (info != null) {
+                        info.users.add(handle);
+                        Log.d(TAG, "service modify users:" + info);
+                        continue;
+                    } else {
+                        info = new ImsServiceInfo(name);
+                        info.users.add(handle);
+                    }
+                    info.controllerFactory = controllerFactory;
 
-                // we will allow the manifest method of declaring manifest features in two cases:
-                // 1) it is the device overlay "default" ImsService, where the features do not
-                // change (the new method can still be used if the default does not define manifest
-                // entries).
-                // 2) using the "compat" ImsService, which only supports manifest query.
-                if (isDeviceService(info)
-                        || mImsServiceControllerFactoryCompat == controllerFactory) {
-                    if (serviceInfo.metaData != null) {
-                        if (serviceInfo.metaData.getBoolean(METADATA_MMTEL_FEATURE, false)) {
-                            info.addFeatureForAllSlots(mNumSlots, ImsFeature.FEATURE_MMTEL);
-                            // only allow FEATURE_EMERGENCY_MMTEL if FEATURE_MMTEL is defined.
-                            if (serviceInfo.metaData.getBoolean(METADATA_EMERGENCY_MMTEL_FEATURE,
-                                    false)) {
-                                info.addFeatureForAllSlots(mNumSlots,
-                                        ImsFeature.FEATURE_EMERGENCY_MMTEL);
+                    // we will allow the manifest method of declaring manifest features in two
+                    // cases:
+
+                    // 1) it is the device overlay "default" ImsService, where the features do not
+                    // change (the new method can still be used if the default does not define
+                    // manifest entries).
+                    // 2) using the "compat" ImsService, which only supports manifest query.
+                    if (isDeviceService(info)
+                            || mImsServiceControllerFactoryCompat == controllerFactory) {
+                        if (serviceInfo.metaData != null) {
+                            if (serviceInfo.metaData.getBoolean(METADATA_MMTEL_FEATURE, false)) {
+                                info.addFeatureForAllSlots(mNumSlots, ImsFeature.FEATURE_MMTEL);
+                                // only allow FEATURE_EMERGENCY_MMTEL if FEATURE_MMTEL is defined.
+                                if (serviceInfo.metaData.getBoolean(
+                                        METADATA_EMERGENCY_MMTEL_FEATURE,
+                                        false)) {
+                                    info.addFeatureForAllSlots(mNumSlots,
+                                            ImsFeature.FEATURE_EMERGENCY_MMTEL);
+                                }
+                            }
+                            if (serviceInfo.metaData.getBoolean(METADATA_RCS_FEATURE, false)) {
+                                info.addFeatureForAllSlots(mNumSlots, ImsFeature.FEATURE_RCS);
                             }
                         }
-                        if (serviceInfo.metaData.getBoolean(METADATA_RCS_FEATURE, false)) {
-                            info.addFeatureForAllSlots(mNumSlots, ImsFeature.FEATURE_RCS);
+                        // Only dynamic query if we are not a compat version of ImsService and the
+                        // default service.
+                        if (mImsServiceControllerFactoryCompat != controllerFactory
+                                && info.getSupportedFeatures().isEmpty()) {
+                            // metadata empty, try dynamic query instead
+                            info.featureFromMetadata = false;
                         }
-                    }
-                    // Only dynamic query if we are not a compat version of ImsService and the
-                    // default service.
-                    if (mImsServiceControllerFactoryCompat != controllerFactory
-                            && info.getSupportedFeatures().isEmpty()) {
-                        // metadata empty, try dynamic query instead
+                    } else {
+                        // We are a carrier service and not using the compat version of ImsService.
                         info.featureFromMetadata = false;
                     }
-                } else {
-                    // We are a carrier service and not using the compat version of ImsService.
-                    info.featureFromMetadata = false;
-                }
-                Log.i(TAG, "service name: " + info.name + ", manifest query: "
-                        + info.featureFromMetadata);
-                // Check manifest permission to be sure that the service declares the correct
-                // permissions. Overridden if the METADATA_OVERRIDE_PERM_CHECK metadata is set to
-                // true.
-                // NOTE: METADATA_OVERRIDE_PERM_CHECK should only be set for testing.
-                if (TextUtils.equals(serviceInfo.permission, Manifest.permission.BIND_IMS_SERVICE)
-                        || serviceInfo.metaData.getBoolean(METADATA_OVERRIDE_PERM_CHECK, false)) {
-                    infos.add(info);
-                } else {
-                    Log.w(TAG, "ImsService is not protected with BIND_IMS_SERVICE permission: "
-                            + info.name);
+                    Log.d(TAG, "service name: " + info.name + ", manifest query: "
+                            + info.featureFromMetadata + ", users: " + info.users);
+                    // Check manifest permission to be sure that the service declares the correct
+                    // permissions. Overridden if the METADATA_OVERRIDE_PERM_CHECK metadata is set
+                    // to true.
+                    // NOTE: METADATA_OVERRIDE_PERM_CHECK should only be set for testing.
+                    if (TextUtils.equals(serviceInfo.permission,
+                            Manifest.permission.BIND_IMS_SERVICE)
+                            || serviceInfo.metaData.getBoolean(METADATA_OVERRIDE_PERM_CHECK,
+                            false)) {
+                        infos.add(info);
+                    } else {
+                        Log.w(TAG, "ImsService is not protected with BIND_IMS_SERVICE permission: "
+                                + info.name);
+                    }
                 }
             }
         }
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceController.java b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
index ea8399f..37c10eb 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
@@ -196,7 +196,7 @@
             }
             if (mCallbacks != null) {
                 // Will trigger an unbind.
-                mCallbacks.imsServiceBindPermanentError(getComponentName());
+                mCallbacks.imsServiceBindPermanentError(getComponentName(), mBoundUser);
             }
         }
 
@@ -217,7 +217,8 @@
         /**
          * Called by ImsServiceController when a new MMTEL or RCS feature has been created.
          */
-        void imsServiceFeatureCreated(int slotId, int feature, ImsServiceController controller);
+        void imsServiceFeatureCreated(int slotId, int subId, int feature,
+                ImsServiceController controller);
         /**
          * Called by ImsServiceController when a new MMTEL or RCS feature has been removed.
          */
@@ -234,7 +235,7 @@
          * Called by the ImsServiceController when there has been an error binding that is
          * not recoverable, such as the ImsService returning a null binder.
          */
-        void imsServiceBindPermanentError(ComponentName name);
+        void imsServiceBindPermanentError(ComponentName name, UserHandle user);
     }
 
     /**
@@ -273,6 +274,7 @@
 
     private boolean mIsBound = false;
     private boolean mIsBinding = false;
+    private UserHandle mBoundUser = null;
     // Set of a pair of slotId->feature
     private Set<ImsFeatureConfiguration.FeatureSlotPair> mImsFeatures;
     private SparseIntArray mSlotIdToSubIdMap;
@@ -337,7 +339,7 @@
                 if (mIsBound) {
                     return;
                 }
-                bind(mImsFeatures, mSlotIdToSubIdMap);
+                bind(mBoundUser, mImsFeatures, mSlotIdToSubIdMap);
             }
         }
     };
@@ -413,17 +415,18 @@
      * @return {@link true} if the service is in the process of being bound, {@link false} if it
      * has failed.
      */
-    public boolean bind(Set<ImsFeatureConfiguration.FeatureSlotPair> imsFeatureSet,
-            SparseIntArray  slotIdToSubIdMap) {
+    public boolean bind(UserHandle user, Set<ImsFeatureConfiguration.FeatureSlotPair> imsFeatureSet,
+            SparseIntArray slotIdToSubIdMap) {
         synchronized (mLock) {
             if (!mIsBound && !mIsBinding) {
                 mIsBinding = true;
+                mBoundUser = user;
                 sanitizeFeatureConfig(imsFeatureSet);
                 mImsFeatures = imsFeatureSet;
                 mSlotIdToSubIdMap = slotIdToSubIdMap;
                 // Set the number of slots that support the feature
                 mImsEnablementTracker.setNumOfSlots(mSlotIdToSubIdMap.size());
-                grantPermissionsToService();
+                grantPermissionsToService(user);
                 Intent imsServiceIntent = new Intent(getServiceInterface()).setComponent(
                         mComponentName);
                 mImsServiceConnection = new ImsServiceConnection();
@@ -432,8 +435,8 @@
                 mLocalLog.log("binding " + imsFeatureSet);
                 Log.i(LOG_TAG, "Binding ImsService:" + mComponentName);
                 try {
-                    boolean bindSucceeded = mContext.bindService(imsServiceIntent,
-                            mImsServiceConnection, serviceFlags);
+                    boolean bindSucceeded = mContext.bindServiceAsUser(imsServiceIntent,
+                            mImsServiceConnection, serviceFlags, user);
                     if (!bindSucceeded) {
                         mLocalLog.log("    binding failed, retrying in "
                                 + mBackoff.getCurrentDelay() + " mS");
@@ -482,6 +485,7 @@
             changeImsServiceFeatures(new HashSet<>(), mSlotIdToSubIdMap);
             mIsBound = false;
             mIsBinding = false;
+            mBoundUser = null;
             setServiceController(null);
             unbindService();
         }
@@ -608,6 +612,13 @@
     }
 
     /**
+     * @return The UserHandle that this controller is bound to or null if bound to no service.
+     */
+    public UserHandle getBoundUser() {
+        return mBoundUser;
+    }
+
+    /**
      * Notify ImsService to enable IMS for the framework. This will trigger IMS registration and
      * trigger ImsFeature status updates.
      */
@@ -766,7 +777,7 @@
 
     // Grant runtime permissions to ImsService. PermissionManager ensures that the ImsService is
     // system/signed before granting permissions.
-    private void grantPermissionsToService() {
+    private void grantPermissionsToService(UserHandle user) {
         mLocalLog.log("grant permissions to " + getComponentName());
         Log.i(LOG_TAG, "Granting Runtime permissions to:" + getComponentName());
         String[] pkgToGrant = {mComponentName.getPackageName()};
@@ -774,8 +785,7 @@
             if (mPermissionManager != null) {
                 CountDownLatch latch = new CountDownLatch(1);
                 mPermissionManager.grantDefaultPermissionsToEnabledImsServices(
-                        pkgToGrant, UserHandle.of(UserHandle.myUserId()), Runnable::run,
-                        isSuccess -> {
+                        pkgToGrant, user, Runnable::run, isSuccess -> {
                             if (isSuccess) {
                                 latch.countDown();
                             } else {
@@ -807,7 +817,8 @@
             Log.i(LOG_TAG, "supports emergency calling on slot " + featurePair.slotId);
         }
         // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
-        mCallbacks.imsServiceFeatureCreated(featurePair.slotId, featurePair.featureType, this);
+        mCallbacks.imsServiceFeatureCreated(featurePair.slotId, subId, featurePair.featureType,
+                this);
     }
 
     // This method should only be called when synchronized on mLock
@@ -978,10 +989,11 @@
     @Override
     public String toString() {
         synchronized (mLock) {
-            return "[ImsServiceController: componentName=" + getComponentName() + ", features="
-                    + mImsFeatures + ", isBinding=" + mIsBinding + ", isBound=" + mIsBound
-                    + ", serviceController=" + getImsServiceController() + ", rebindDelay="
-                    + getRebindDelay() + "]";
+            return "[ImsServiceController: componentName=" + getComponentName() + ", boundUser="
+                    + mBoundUser + ", features=" + mImsFeatures + ", isBinding=" + mIsBinding
+                    + ", isBound=" + mIsBound + ", serviceController=" + getImsServiceController()
+                    + ", rebindDelay=" + getRebindDelay() + ", slotToSubIdMap=" + mSlotIdToSubIdMap
+                    + "]";
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java b/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java
index 564cdcc..a4b4f46 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.IBinder;
+import android.os.UserHandle;
 import android.telephony.ims.aidl.IImsServiceController;
 import android.telephony.ims.stub.ImsFeatureConfiguration;
 import android.util.Log;
@@ -42,14 +43,16 @@
         private static final String LOG_TAG = "ImsServiceFeatureQuery";
 
         private final ComponentName mName;
+        private final UserHandle mUser;
         private final String mIntentFilter;
         // Track the status of whether or not the Service has died in case we need to permanently
         // unbind (see onNullBinding below).
         private boolean mIsServiceConnectionDead = false;
 
 
-        ImsServiceFeatureQuery(ComponentName name, String intentFilter) {
+        ImsServiceFeatureQuery(ComponentName name, UserHandle user, String intentFilter) {
             mName = name;
+            mUser = user;
             mIntentFilter = intentFilter;
         }
 
@@ -62,7 +65,8 @@
             Intent imsServiceIntent = new Intent(mIntentFilter).setComponent(mName);
             int serviceFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                     | Context.BIND_IMPORTANT;
-            boolean bindStarted = mContext.bindService(imsServiceIntent, this, serviceFlags);
+            boolean bindStarted = mContext.bindServiceAsUser(imsServiceIntent, this,
+                    serviceFlags, mUser);
             if (!bindStarted) {
                 // Docs say to unbind if this fails.
                 cleanup();
@@ -78,7 +82,7 @@
             } else {
                 Log.w(LOG_TAG, "onServiceConnected: " + name + " binder null.");
                 cleanup();
-                mListener.onPermanentError(name);
+                mListener.onPermanentError(name, mUser);
             }
         }
 
@@ -103,7 +107,7 @@
             // permanently unbind and instead let the automatic rebind occur.
             if (mIsServiceConnectionDead) return;
             cleanup();
-            mListener.onPermanentError(name);
+            mListener.onPermanentError(name, mUser);
         }
 
         private void queryImsFeatures(IImsServiceController controller) {
@@ -154,7 +158,7 @@
         /**
          * Called when a query has failed due to a permanent error and should not be retried.
          */
-        void onPermanentError(ComponentName name);
+        void onPermanentError(ComponentName name, UserHandle user);
     }
 
     // Maps an active ImsService query (by Package Name String) its query.
@@ -171,16 +175,17 @@
     /**
      * Starts an ImsService feature query for the ComponentName and Intent specified.
      * @param name The ComponentName of the ImsService being queried.
+     * @param user The User associated with the request.
      * @param intentFilter The Intent filter that the ImsService specified.
      * @return true if the query started, false if it was unable to start.
      */
-    public boolean startQuery(ComponentName name, String intentFilter) {
+    public boolean startQuery(ComponentName name, UserHandle user, String intentFilter) {
         synchronized (mLock) {
             if (mActiveQueries.containsKey(name)) {
                 // We already have an active query, wait for it to return.
                 return true;
             }
-            ImsServiceFeatureQuery query = new ImsServiceFeatureQuery(name, intentFilter);
+            ImsServiceFeatureQuery query = new ImsServiceFeatureQuery(name, user, intentFilter);
             mActiveQueries.put(name, query);
             return query.start();
         }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index 26b6e18..d47f05b 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -1797,7 +1797,8 @@
                                 dialArgs.intentExtras);
                     }
                 };
-                EmergencyStateTracker.getInstance().exitEmergencyCallbackMode(onComplete);
+                EmergencyStateTracker.getInstance().exitEmergencyCallbackMode(onComplete,
+                        TelephonyManager.STOP_REASON_OUTGOING_NORMAL_CALL_INITIATED);
             } else {
                 try {
                     getEcbmInterface().exitEmergencyCallbackMode();
@@ -2884,7 +2885,7 @@
         boolean rejectCall = false;
 
         if (mFeatureFlags.preventHangupDuringCallMerge()) {
-            if (imsCall.isCallSessionMergePending()) {
+            if (imsCall != null && imsCall.isCallSessionMergePending()) {
                 if (DBG) log("hangup call failed during call merge");
 
                 throw new CallStateException("can not hangup during call merge");
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index e43bf3c..8ef5a8f 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -1480,7 +1480,10 @@
                 satelliteController.carrierId,
                 satelliteController.countOfSatelliteAllowedStateChangedEvents,
                 satelliteController.countOfSuccessfulLocationQueries,
-                satelliteController.countOfFailedLocationQueries);
+                satelliteController.countOfFailedLocationQueries,
+                satelliteController.countOfP2PSmsAvailableNotificationShown,
+                satelliteController.countOfP2PSmsAvailableNotificationRemoved,
+                satelliteController.isNtnOnlyCarrier);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteSession satelliteSession) {
@@ -1502,7 +1505,10 @@
                 satelliteSession.carrierId,
                 satelliteSession.countOfSatelliteNotificationDisplayed,
                 satelliteSession.countOfAutoExitDueToScreenOff,
-                satelliteSession.countOfAutoExitDueToTnNetwork);
+                satelliteSession.countOfAutoExitDueToTnNetwork,
+                satelliteSession.isEmergency,
+                satelliteSession.isNtnOnlyCarrier,
+                satelliteSession.maxInactivityDurationSec);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteIncomingDatagram stats) {
@@ -1512,7 +1518,8 @@
                 stats.datagramSizeBytes,
                 stats.datagramTransferTimeMillis,
                 stats.isDemoMode,
-                stats.carrierId);
+                stats.carrierId,
+                stats.isNtnOnlyCarrier);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteOutgoingDatagram stats) {
@@ -1523,7 +1530,8 @@
                 stats.datagramSizeBytes,
                 stats.datagramTransferTimeMillis,
                 stats.isDemoMode,
-                stats.carrierId);
+                stats.carrierId,
+                stats.isNtnOnlyCarrier);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteProvision stats) {
@@ -1533,7 +1541,8 @@
                 stats.provisioningTimeSec,
                 stats.isProvisionRequest,
                 stats.isCanceled,
-                stats.carrierId);
+                stats.carrierId,
+                stats.isNtnOnlyCarrier);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteSosMessageRecommender stats) {
@@ -1548,7 +1557,8 @@
                 stats.recommendingHandoverType,
                 stats.isSatelliteAllowedInCurrentLocation,
                 stats.isWifiConnected,
-                stats.carrierId);
+                stats.carrierId,
+                stats.isNtnOnlyCarrier);
     }
 
     private static StatsEvent buildStatsEvent(DataNetworkValidation stats) {
@@ -1629,7 +1639,8 @@
                 stats.countryCodes,
                 stats.configDataSource,
                 stats.carrierId,
-                stats.triggeringEvent);
+                stats.triggeringEvent,
+                stats.isNtnOnlyCarrier);
     }
 
     /** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */
diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
index cc06ca6..2ddc604 100644
--- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -177,7 +177,6 @@
 
     /** Maximum number of Satellite relevant stats to store between pulls. */
     private final int mMaxNumSatelliteStats;
-    private final int mMaxNumSatelliteControllerStats = 1;
     private final int mMaxNumCarrierRoamingSatelliteSessionStats = 1;
 
     /** Maximum number of data network validation to store during pulls. */
@@ -710,76 +709,78 @@
 
     /** Adds a new {@link SatelliteController} to the storage. */
     public synchronized void addSatelliteControllerStats(SatelliteController stats) {
-        // SatelliteController is a single data point
-        SatelliteController[] atomArray = mAtoms.satelliteController;
-        if (atomArray == null || atomArray.length == 0) {
-            atomArray = new SatelliteController[] {new SatelliteController()};
+        // find existing satellite controller atom with same carrier ID.
+        SatelliteController existingStats = find(stats);
+        if (existingStats != null) {
+            existingStats.countOfSatelliteServiceEnablementsSuccess
+                    += stats.countOfSatelliteServiceEnablementsSuccess;
+            existingStats.countOfSatelliteServiceEnablementsFail
+                    += stats.countOfSatelliteServiceEnablementsFail;
+            existingStats.countOfOutgoingDatagramSuccess
+                    += stats.countOfOutgoingDatagramSuccess;
+            existingStats.countOfOutgoingDatagramFail
+                    += stats.countOfOutgoingDatagramFail;
+            existingStats.countOfIncomingDatagramSuccess
+                    += stats.countOfIncomingDatagramSuccess;
+            existingStats.countOfIncomingDatagramFail
+                    += stats.countOfIncomingDatagramFail;
+            existingStats.countOfDatagramTypeSosSmsSuccess
+                    += stats.countOfDatagramTypeSosSmsSuccess;
+            existingStats.countOfDatagramTypeSosSmsFail
+                    += stats.countOfDatagramTypeSosSmsFail;
+            existingStats.countOfDatagramTypeLocationSharingSuccess
+                    += stats.countOfDatagramTypeLocationSharingSuccess;
+            existingStats.countOfDatagramTypeLocationSharingFail
+                    += stats.countOfDatagramTypeLocationSharingFail;
+            existingStats.countOfProvisionSuccess
+                    += stats.countOfProvisionSuccess;
+            existingStats.countOfProvisionFail
+                    += stats.countOfProvisionFail;
+            existingStats.countOfDeprovisionSuccess
+                    += stats.countOfDeprovisionSuccess;
+            existingStats.countOfDeprovisionFail
+                    += stats.countOfDeprovisionFail;
+            existingStats.totalServiceUptimeSec
+                    += stats.totalServiceUptimeSec;
+            existingStats.totalBatteryConsumptionPercent
+                    += stats.totalBatteryConsumptionPercent;
+            existingStats.totalBatteryChargedTimeSec
+                    += stats.totalBatteryChargedTimeSec;
+            existingStats.countOfDemoModeSatelliteServiceEnablementsSuccess
+                    += stats.countOfDemoModeSatelliteServiceEnablementsSuccess;
+            existingStats.countOfDemoModeSatelliteServiceEnablementsFail
+                    += stats.countOfDemoModeSatelliteServiceEnablementsFail;
+            existingStats.countOfDemoModeOutgoingDatagramSuccess
+                    += stats.countOfDemoModeOutgoingDatagramSuccess;
+            existingStats.countOfDemoModeOutgoingDatagramFail
+                    += stats.countOfDemoModeOutgoingDatagramFail;
+            existingStats.countOfDemoModeIncomingDatagramSuccess
+                    += stats.countOfDemoModeIncomingDatagramSuccess;
+            existingStats.countOfDemoModeIncomingDatagramFail
+                    += stats.countOfDemoModeIncomingDatagramFail;
+            existingStats.countOfDatagramTypeKeepAliveSuccess
+                    += stats.countOfDatagramTypeKeepAliveSuccess;
+            existingStats.countOfDatagramTypeKeepAliveFail
+                    += stats.countOfDatagramTypeKeepAliveFail;
+            existingStats.countOfAllowedSatelliteAccess += stats.countOfAllowedSatelliteAccess;
+            existingStats.countOfDisallowedSatelliteAccess
+                    += stats.countOfDisallowedSatelliteAccess;
+            existingStats.countOfSatelliteAccessCheckFail += stats.countOfSatelliteAccessCheckFail;
+            // Does not update isProvisioned and carrierId due to they are dimension fields.
+            existingStats.countOfSatelliteAllowedStateChangedEvents
+                    += stats.countOfSatelliteAllowedStateChangedEvents;
+            existingStats.countOfSuccessfulLocationQueries +=
+                    stats.countOfSuccessfulLocationQueries;
+            existingStats.countOfFailedLocationQueries += stats.countOfFailedLocationQueries;
+            existingStats.countOfP2PSmsAvailableNotificationShown
+                    += stats.countOfP2PSmsAvailableNotificationShown;
+            existingStats.countOfP2PSmsAvailableNotificationRemoved
+                    += stats.countOfP2PSmsAvailableNotificationRemoved;
+            // Does not update isNtnOnlyCarrier due to it is a dimension field.
+        } else {
+            mAtoms.satelliteController = insertAtRandomPlace(mAtoms.satelliteController, stats,
+                    mMaxNumSatelliteStats);
         }
-
-        SatelliteController atom = atomArray[0];
-        atom.countOfSatelliteServiceEnablementsSuccess
-                += stats.countOfSatelliteServiceEnablementsSuccess;
-        atom.countOfSatelliteServiceEnablementsFail
-                += stats.countOfSatelliteServiceEnablementsFail;
-        atom.countOfOutgoingDatagramSuccess
-                += stats.countOfOutgoingDatagramSuccess;
-        atom.countOfOutgoingDatagramFail
-                += stats.countOfOutgoingDatagramFail;
-        atom.countOfIncomingDatagramSuccess
-                += stats.countOfIncomingDatagramSuccess;
-        atom.countOfIncomingDatagramFail
-                += stats.countOfIncomingDatagramFail;
-        atom.countOfDatagramTypeSosSmsSuccess
-                += stats.countOfDatagramTypeSosSmsSuccess;
-        atom.countOfDatagramTypeSosSmsFail
-                += stats.countOfDatagramTypeSosSmsFail;
-        atom.countOfDatagramTypeLocationSharingSuccess
-                += stats.countOfDatagramTypeLocationSharingSuccess;
-        atom.countOfDatagramTypeLocationSharingFail
-                += stats.countOfDatagramTypeLocationSharingFail;
-        atom.countOfProvisionSuccess
-                += stats.countOfProvisionSuccess;
-        atom.countOfProvisionFail
-                += stats.countOfProvisionFail;
-        atom.countOfDeprovisionSuccess
-                += stats.countOfDeprovisionSuccess;
-        atom.countOfDeprovisionFail
-                += stats.countOfDeprovisionFail;
-        atom.totalServiceUptimeSec
-                += stats.totalServiceUptimeSec;
-        atom.totalBatteryConsumptionPercent
-                += stats.totalBatteryConsumptionPercent;
-        atom.totalBatteryChargedTimeSec
-                += stats.totalBatteryChargedTimeSec;
-        atom.countOfDemoModeSatelliteServiceEnablementsSuccess
-                += stats.countOfDemoModeSatelliteServiceEnablementsSuccess;
-        atom.countOfDemoModeSatelliteServiceEnablementsFail
-                += stats.countOfDemoModeSatelliteServiceEnablementsFail;
-        atom.countOfDemoModeOutgoingDatagramSuccess
-                += stats.countOfDemoModeOutgoingDatagramSuccess;
-        atom.countOfDemoModeOutgoingDatagramFail
-                += stats.countOfDemoModeOutgoingDatagramFail;
-        atom.countOfDemoModeIncomingDatagramSuccess
-                += stats.countOfDemoModeIncomingDatagramSuccess;
-        atom.countOfDemoModeIncomingDatagramFail
-                += stats.countOfDemoModeIncomingDatagramFail;
-        atom.countOfDatagramTypeKeepAliveSuccess
-                += stats.countOfDatagramTypeKeepAliveSuccess;
-        atom.countOfDatagramTypeKeepAliveFail
-                += stats.countOfDatagramTypeKeepAliveFail;
-        atom.countOfAllowedSatelliteAccess += stats.countOfAllowedSatelliteAccess;
-        atom.countOfDisallowedSatelliteAccess += stats.countOfDisallowedSatelliteAccess;
-        atom.countOfSatelliteAccessCheckFail += stats.countOfSatelliteAccessCheckFail;
-
-        atom.isProvisioned = stats.isProvisioned;
-        atom.carrierId = stats.carrierId;
-
-        atom.countOfSatelliteAllowedStateChangedEvents
-                += stats.countOfSatelliteAllowedStateChangedEvents;
-        atom.countOfSuccessfulLocationQueries += stats.countOfSuccessfulLocationQueries;
-        atom.countOfFailedLocationQueries += stats.countOfFailedLocationQueries;
-
-        mAtoms.satelliteController = atomArray;
         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
     }
 
@@ -861,26 +862,25 @@
     /** Adds a new {@link CarrierRoamingSatelliteControllerStats} to the storage. */
     public synchronized void addCarrierRoamingSatelliteControllerStats(
             CarrierRoamingSatelliteControllerStats stats) {
-        // CarrierRoamingSatelliteController is a single data point
-        CarrierRoamingSatelliteControllerStats[] atomArray =
-                mAtoms.carrierRoamingSatelliteControllerStats;
-        if (atomArray == null || atomArray.length == 0) {
-            atomArray = new CarrierRoamingSatelliteControllerStats[] {new
-                    CarrierRoamingSatelliteControllerStats()};
+        CarrierRoamingSatelliteControllerStats existingStats = find(stats);
+        if (existingStats != null) {
+            existingStats.countOfEntitlementStatusQueryRequest +=
+                    stats.countOfEntitlementStatusQueryRequest;
+            existingStats.countOfSatelliteConfigUpdateRequest +=
+                    stats.countOfSatelliteConfigUpdateRequest;
+            existingStats.countOfSatelliteNotificationDisplayed +=
+                    stats.countOfSatelliteNotificationDisplayed;
+            existingStats.satelliteSessionGapMinSec = stats.satelliteSessionGapMinSec;
+            existingStats.satelliteSessionGapAvgSec = stats.satelliteSessionGapAvgSec;
+            existingStats.satelliteSessionGapMaxSec = stats.satelliteSessionGapMaxSec;
+            // Does not update configDataSource, carrierId, isDeviceEntitled, due to  they are
+            // dimension fields.
+            existingStats.isDeviceEntitled = stats.isDeviceEntitled;
+        } else {
+            mAtoms.carrierRoamingSatelliteControllerStats = insertAtRandomPlace(
+                    mAtoms.carrierRoamingSatelliteControllerStats, stats, mMaxNumSatelliteStats);
         }
 
-        CarrierRoamingSatelliteControllerStats atom = atomArray[0];
-        atom.configDataSource = stats.configDataSource;
-        atom.countOfEntitlementStatusQueryRequest += stats.countOfEntitlementStatusQueryRequest;
-        atom.countOfSatelliteConfigUpdateRequest += stats.countOfSatelliteConfigUpdateRequest;
-        atom.countOfSatelliteNotificationDisplayed += stats.countOfSatelliteNotificationDisplayed;
-        atom.satelliteSessionGapMinSec = stats.satelliteSessionGapMinSec;
-        atom.satelliteSessionGapAvgSec = stats.satelliteSessionGapAvgSec;
-        atom.satelliteSessionGapMaxSec = stats.satelliteSessionGapMaxSec;
-        atom.carrierId = stats.carrierId;
-        atom.isDeviceEntitled = stats.isDeviceEntitled;
-
-        mAtoms.carrierRoamingSatelliteControllerStats = atomArray;
         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
     }
 
@@ -1827,7 +1827,7 @@
             atoms.outgoingShortCodeSms = sanitizeAtoms(atoms.outgoingShortCodeSms,
                     OutgoingShortCodeSms.class, mMaxOutgoingShortCodeSms);
             atoms.satelliteController = sanitizeAtoms(atoms.satelliteController,
-                            SatelliteController.class, mMaxNumSatelliteControllerStats);
+                            SatelliteController.class, mMaxNumSatelliteStats);
             atoms.satelliteSession = sanitizeAtoms(atoms.satelliteSession,
                     SatelliteSession.class, mMaxNumSatelliteStats);
             atoms.satelliteIncomingDatagram = sanitizeAtoms(atoms.satelliteIncomingDatagram,
@@ -1850,7 +1850,7 @@
                     mMaxNumSatelliteStats);
             atoms.carrierRoamingSatelliteControllerStats = sanitizeAtoms(
                     atoms.carrierRoamingSatelliteControllerStats,
-                    CarrierRoamingSatelliteControllerStats.class, mMaxNumSatelliteControllerStats);
+                    CarrierRoamingSatelliteControllerStats.class, mMaxNumSatelliteStats);
             atoms.satelliteEntitlement = sanitizeAtoms(atoms.satelliteEntitlement,
                     SatelliteEntitlement.class, mMaxNumSatelliteStats);
             atoms.satelliteConfigUpdater = sanitizeAtoms(atoms.satelliteConfigUpdater,
@@ -2346,7 +2346,9 @@
                     && stats.countOfSatelliteNotificationDisplayed
                     == key.countOfSatelliteNotificationDisplayed
                     && stats.countOfAutoExitDueToScreenOff == key.countOfAutoExitDueToScreenOff
-                    && stats.countOfAutoExitDueToTnNetwork == key.countOfAutoExitDueToTnNetwork) {
+                    && stats.countOfAutoExitDueToTnNetwork == key.countOfAutoExitDueToTnNetwork
+                    && stats.isEmergency == key.isEmergency
+                    && stats.maxInactivityDurationSec == key.maxInactivityDurationSec) {
                 return stats;
             }
         }
@@ -2366,6 +2368,8 @@
                     && stats.cellularServiceState == key.cellularServiceState
                     && stats.isMultiSim == key.isMultiSim
                     && stats.recommendingHandoverType == key.recommendingHandoverType
+                    && stats.isSatelliteAllowedInCurrentLocation
+                    == key.isSatelliteAllowedInCurrentLocation
                     && stats.isWifiConnected == key.isWifiConnected
                     && stats.carrierId == key.carrierId) {
                 return stats;
@@ -2392,6 +2396,38 @@
     }
 
     /**
+     * Returns the SatelliteController atom with the matching `carrier_id`, `is_provisioned`, and
+     * `is_ntn_only_carrier` values, or {@code null} if does not exist.
+     */
+    private @Nullable SatelliteController find(SatelliteController key) {
+        for (SatelliteController stats : mAtoms.satelliteController) {
+            if (stats.carrierId == key.carrierId
+                    && stats.isProvisioned == key.isProvisioned
+                    && stats.isNtnOnlyCarrier == key.isNtnOnlyCarrier) {
+                return stats;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns CarrierRoamingSatelliteControllerStats atom that has same carrier_id value or
+     * {@code null} if does not exist.
+     */
+    private @Nullable CarrierRoamingSatelliteControllerStats find(
+            CarrierRoamingSatelliteControllerStats key) {
+        for (CarrierRoamingSatelliteControllerStats stats :
+                mAtoms.carrierRoamingSatelliteControllerStats) {
+            if (stats.carrierId == key.carrierId
+                    && stats.configDataSource == key.configDataSource
+                    && stats.isDeviceEntitled == key.isDeviceEntitled) {
+                return stats;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns SatelliteEntitlement atom that has same values or {@code null} if it does not exist.
      */
     private @Nullable SatelliteEntitlement find(SatelliteEntitlement key) {
diff --git a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
index 71c1bf3..c17c8ab 100644
--- a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
+++ b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
@@ -100,6 +100,9 @@
         private final int mCountOfSatelliteAllowedStateChangedEvents;
         private final int mCountOfSuccessfulLocationQueries;
         private final int mCountOfFailedLocationQueries;
+        private final int mCountOfP2PSmsAvailableNotificationShown;
+        private final int mCountOfP2PSmsAvailableNotificationRemoved;
+        private static boolean sIsNtnOnlyCarrier;
 
         private SatelliteControllerParams(Builder builder) {
             this.mCountOfSatelliteServiceEnablementsSuccess =
@@ -162,6 +165,15 @@
                     builder.mCountOfSuccessfulLocationQueries;
             this.mCountOfFailedLocationQueries =
                     builder.mCountOfFailedLocationQueries;
+            this.mCountOfP2PSmsAvailableNotificationShown =
+                    builder.mCountOfP2PSmsAvailableNotificationShown;
+            this.mCountOfP2PSmsAvailableNotificationRemoved =
+                    builder.mCountOfP2PSmsAvailableNotificationRemoved;
+
+            // Carrier ID value should be updated only when it is meaningful.
+            if (builder.mIsNtnOnlyCarrier.isPresent()) {
+                this.sIsNtnOnlyCarrier = builder.mIsNtnOnlyCarrier.get();
+            }
         }
 
         public int getCountOfSatelliteServiceEnablementsSuccess() {
@@ -276,11 +288,11 @@
             return mCountOfSatelliteAccessCheckFail;
         }
 
-        public boolean isProvisioned() {
+        public static boolean isProvisioned() {
             return sIsProvisioned;
         }
 
-        public int getCarrierId() {
+        public static int getCarrierId() {
             return sCarrierId;
         }
 
@@ -296,6 +308,18 @@
             return mCountOfFailedLocationQueries;
         }
 
+        public int getCountOfP2PSmsAvailableNotificationShown() {
+            return mCountOfP2PSmsAvailableNotificationShown;
+        }
+
+        public int getCountOfP2PSmsAvailableNotificationRemoved() {
+            return mCountOfP2PSmsAvailableNotificationRemoved;
+        }
+
+        public static boolean isNtnOnlyCarrier() {
+            return sIsNtnOnlyCarrier;
+        }
+
         /**
          * A builder class to create {@link SatelliteControllerParams} data structure class
          */
@@ -333,6 +357,9 @@
             private int mCountOfSatelliteAllowedStateChangedEvents = 0;
             private int mCountOfSuccessfulLocationQueries = 0;
             private int mCountOfFailedLocationQueries = 0;
+            private int mCountOfP2PSmsAvailableNotificationShown = 0;
+            private int mCountOfP2PSmsAvailableNotificationRemoved = 0;
+            private Optional<Boolean> mIsNtnOnlyCarrier = Optional.empty();
 
             /**
              * Sets countOfSatelliteServiceEnablementsSuccess value of {@link SatelliteController}
@@ -661,6 +688,37 @@
             }
 
             /**
+             * Sets countOfP2PSmsAvailableNotificationShown value of {@link SatelliteController}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfP2PSmsAvailableNotificationShown(
+                    int countOfP2PSmsAvailableNotificationShown) {
+                this.mCountOfP2PSmsAvailableNotificationShown =
+                        countOfP2PSmsAvailableNotificationShown;
+                return this;
+            }
+
+            /**
+             * Sets countOfP2PSmsAvailableNotificationRemoved value of {@link SatelliteController}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfP2PSmsAvailableNotificationRemoved(
+                    int countOfP2PSmsAvailableNotificationRemoved) {
+                this.mCountOfP2PSmsAvailableNotificationRemoved =
+                        countOfP2PSmsAvailableNotificationRemoved;
+                return this;
+            }
+
+            /**
+             * Sets isNtnOnlyCarrier value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier) {
+                this.mIsNtnOnlyCarrier = Optional.of(isNtnOnlyCarrier);
+                return this;
+            }
+
+            /**
              * Returns ControllerParams, which contains whole component of
              * {@link SatelliteController} atom
              */
@@ -715,6 +773,11 @@
                     + mCountOfSatelliteAllowedStateChangedEvents
                     + ", countOfSuccessfulLocationQueries=" + mCountOfSuccessfulLocationQueries
                     + ", countOfFailedLocationQueries=" + mCountOfFailedLocationQueries
+                    + ", countOfP2PSmsAvailableNotificationShown="
+                    + mCountOfP2PSmsAvailableNotificationShown
+                    + ", countOfP2PSmsAvailableNotificationRemoved="
+                    + mCountOfP2PSmsAvailableNotificationRemoved
+                    + ", isNtnOnlyCarrier=" + sIsNtnOnlyCarrier
                     + ")";
         }
     }
@@ -740,7 +803,9 @@
         private final int mCountOfSatelliteNotificationDisplayed;
         private final int mCountOfAutoExitDueToScreenOff;
         private final int mCountOfAutoExitDueToTnNetwork;
-
+        private final boolean mIsEmergency;
+        private final int mMaxInactivityDurationSec;
+        private final boolean mIsNtnOnlyCarrier;
 
         private SatelliteSessionParams(Builder builder) {
             this.mSatelliteServiceInitializationResult =
@@ -762,6 +827,9 @@
                     builder.mCountOfSatelliteNotificationDisplayed;
             this.mCountOfAutoExitDueToScreenOff = builder.mCountOfAutoExitDueToScreenOff;
             this.mCountOfAutoExitDueToTnNetwork = builder.mCountOfAutoExitDueToTnNetwork;
+            this.mIsEmergency = builder.mIsEmergency;
+            this.mIsNtnOnlyCarrier = builder.mIsNtnOnlyCarrier;
+            this.mMaxInactivityDurationSec = builder.mMaxInactivityDurationSec;
         }
 
         public int getSatelliteServiceInitializationResult() {
@@ -828,6 +896,18 @@
             return mCountOfAutoExitDueToTnNetwork;
         }
 
+        public boolean getIsEmergency() {
+            return mIsEmergency;
+        }
+
+        public boolean isNtnOnlyCarrier() {
+            return mIsNtnOnlyCarrier;
+        }
+
+        public int getMaxInactivityDurationSec() {
+            return mMaxInactivityDurationSec;
+        }
+
         /**
          * A builder class to create {@link SatelliteSessionParams} data structure class
          */
@@ -849,6 +929,9 @@
             private int mCountOfSatelliteNotificationDisplayed = -1;
             private int mCountOfAutoExitDueToScreenOff = -1;
             private int mCountOfAutoExitDueToTnNetwork = -1;
+            private boolean mIsEmergency = false;
+            private boolean mIsNtnOnlyCarrier = false;
+            private int mMaxInactivityDurationSec = -1;
 
             /**
              * Sets satelliteServiceInitializationResult value of {@link SatelliteSession}
@@ -967,6 +1050,27 @@
                 return this;
             }
 
+            /** Sets whether enabled satellite session is for emergency or not. */
+            public Builder setIsEmergency(boolean isEmergency) {
+                this.mIsEmergency = isEmergency;
+                return this;
+            }
+
+            /**
+             * Sets isNtnOnlyCarrier value of {@link SatelliteSession} atom
+             * then returns Builder class
+            */
+            public Builder setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier) {
+                this.mIsNtnOnlyCarrier = isNtnOnlyCarrier;
+                return this;
+            }
+
+            /** Sets the max user inactivity duration in seconds. */
+            public Builder setMaxInactivityDurationSec(int maxInactivityDurationSec) {
+                this.mMaxInactivityDurationSec = maxInactivityDurationSec;
+                return this;
+            }
+
             /**
              * Returns SessionParams, which contains whole component of
              * {@link SatelliteSession} atom
@@ -997,6 +1101,9 @@
                     + mCountOfSatelliteNotificationDisplayed
                     + ", CountOfAutoExitDueToScreenOff" + mCountOfAutoExitDueToScreenOff
                     + ", CountOfAutoExitDueToTnNetwork" + mCountOfAutoExitDueToTnNetwork
+                    + ", IsEmergency=" + mIsEmergency
+                    + ", IsNtnOnlyCarrier=" + mIsNtnOnlyCarrier
+                    + ", MaxInactivityDurationSec=" + mMaxInactivityDurationSec
                     + ")";
         }
     }
@@ -1011,6 +1118,7 @@
         private final long mDatagramTransferTimeMillis;
         private final boolean mIsDemoMode;
         private final int mCarrierId;
+        private final boolean mIsNtnOnlyCarrier;
 
         private SatelliteIncomingDatagramParams(Builder builder) {
             this.mResultCode = builder.mResultCode;
@@ -1018,6 +1126,7 @@
             this.mDatagramTransferTimeMillis = builder.mDatagramTransferTimeMillis;
             this.mIsDemoMode = builder.mIsDemoMode;
             this.mCarrierId = builder.mCarrierId;
+            this.mIsNtnOnlyCarrier = builder.mIsNtnOnlyCarrier;
         }
 
         public int getResultCode() {
@@ -1040,6 +1149,10 @@
             return mCarrierId;
         }
 
+        public boolean isNtnOnlyCarrier() {
+            return mIsNtnOnlyCarrier;
+        }
+
         /**
          * A builder class to create {@link SatelliteIncomingDatagramParams} data structure class
          */
@@ -1049,6 +1162,7 @@
             private long mDatagramTransferTimeMillis = -1;
             private boolean mIsDemoMode = false;
             private int mCarrierId = UNKNOWN_CARRIER_ID;
+            private boolean mIsNtnOnlyCarrier = false;
 
             /**
              * Sets resultCode value of {@link SatelliteIncomingDatagram} atom
@@ -1093,6 +1207,15 @@
             }
 
             /**
+             * Sets isNtnOnlyCarrier value of {@link SatelliteIncomingDatagram} atom
+             * then returns Builder class
+            */
+            public Builder setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier) {
+                this.mIsNtnOnlyCarrier = isNtnOnlyCarrier;
+                return this;
+            }
+
+            /**
              * Returns IncomingDatagramParams, which contains whole component of
              * {@link SatelliteIncomingDatagram} atom
              */
@@ -1110,6 +1233,7 @@
                     + ", datagramTransferTimeMillis=" + mDatagramTransferTimeMillis
                     + ", isDemoMode=" + mIsDemoMode
                     + ", CarrierId=" + mCarrierId
+                    + ", isNtnOnlyCarrier=" + mIsNtnOnlyCarrier
                     + ")";
         }
     }
@@ -1125,6 +1249,7 @@
         private final long mDatagramTransferTimeMillis;
         private final boolean mIsDemoMode;
         private final int mCarrierId;
+        private final boolean mIsNtnOnlyCarrier;
 
         private SatelliteOutgoingDatagramParams(Builder builder) {
             this.mDatagramType = builder.mDatagramType;
@@ -1133,6 +1258,7 @@
             this.mDatagramTransferTimeMillis = builder.mDatagramTransferTimeMillis;
             this.mIsDemoMode = builder.mIsDemoMode;
             this.mCarrierId = builder.mCarrierId;
+            this.mIsNtnOnlyCarrier = builder.mIsNtnOnlyCarrier;
         }
 
         public int getDatagramType() {
@@ -1159,6 +1285,10 @@
             return mCarrierId;
         }
 
+        public boolean isNtnOnlyCarrier() {
+            return mIsNtnOnlyCarrier;
+        }
+
         /**
          * A builder class to create {@link SatelliteOutgoingDatagramParams} data structure class
          */
@@ -1169,6 +1299,7 @@
             private long mDatagramTransferTimeMillis = -1;
             private boolean mIsDemoMode = false;
             private int mCarrierId = UNKNOWN_CARRIER_ID;
+            private boolean mIsNtnOnlyCarrier = false;
 
             /**
              * Sets datagramType value of {@link SatelliteOutgoingDatagram} atom
@@ -1222,6 +1353,15 @@
             }
 
             /**
+             * Sets isNtnOnlyCarrier value of {@link SatelliteOutgoingDatagram} atom
+             * then returns Builder class
+            */
+            public Builder setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier) {
+                this.mIsNtnOnlyCarrier = isNtnOnlyCarrier;
+                return this;
+            }
+
+            /**
              * Returns OutgoingDatagramParams, which contains whole component of
              * {@link SatelliteOutgoingDatagram} atom
              */
@@ -1240,6 +1380,7 @@
                     + ", datagramTransferTimeMillis=" + mDatagramTransferTimeMillis
                     + ", isDemoMode=" + mIsDemoMode
                     + ", CarrierId=" + mCarrierId
+                    + ", isNtnOnlyCarrier=" + mIsNtnOnlyCarrier
                     + ")";
         }
     }
@@ -1254,6 +1395,7 @@
         private final boolean mIsProvisionRequest;
         private final boolean mIsCanceled;
         private final int mCarrierId;
+        private final boolean mIsNtnOnlyCarrier;
 
         private SatelliteProvisionParams(Builder builder) {
             this.mResultCode = builder.mResultCode;
@@ -1261,6 +1403,7 @@
             this.mIsProvisionRequest = builder.mIsProvisionRequest;
             this.mIsCanceled = builder.mIsCanceled;
             this.mCarrierId = builder.mCarrierId;
+            this.mIsNtnOnlyCarrier = builder.mIsNtnOnlyCarrier;
         }
 
         public int getResultCode() {
@@ -1283,6 +1426,10 @@
             return mCarrierId;
         }
 
+        public boolean isNtnOnlyCarrier() {
+            return mIsNtnOnlyCarrier;
+        }
+
         /**
          * A builder class to create {@link SatelliteProvisionParams} data structure class
          */
@@ -1292,6 +1439,7 @@
             private boolean mIsProvisionRequest = false;
             private boolean mIsCanceled = false;
             private int mCarrierId = UNKNOWN_CARRIER_ID;
+            private boolean mIsNtnOnlyCarrier = false;
 
             /**
              * Sets resultCode value of {@link SatelliteProvision} atom
@@ -1336,6 +1484,15 @@
             }
 
             /**
+             * Sets isNtnOnlyCarrier value of {@link SatelliteProvision} atom
+             * then returns Builder class
+            */
+            public Builder setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier) {
+                this.mIsNtnOnlyCarrier = isNtnOnlyCarrier;
+                return this;
+            }
+
+            /**
              * Returns ProvisionParams, which contains whole component of
              * {@link SatelliteProvision} atom
              */
@@ -1353,6 +1510,7 @@
                     + ", isProvisionRequest=" + mIsProvisionRequest
                     + ", isCanceled" + mIsCanceled
                     + ", CarrierId=" + mCarrierId
+                    + ", isNtnOnlyCarrier=" + mIsNtnOnlyCarrier
                     + ")";
         }
     }
@@ -1371,6 +1529,7 @@
         private final boolean mIsSatelliteAllowedInCurrentLocation;
         private final boolean mIsWifiConnected;
         private final int mCarrierId;
+        private final boolean mIsNtnOnlyCarrier;
 
         private SatelliteSosMessageRecommenderParams(Builder builder) {
             this.mIsDisplaySosMessageSent = builder.mIsDisplaySosMessageSent;
@@ -1383,6 +1542,7 @@
                     builder.mIsSatelliteAllowedInCurrentLocation;
             this.mIsWifiConnected = builder.mIsWifiConnected;
             this.mCarrierId = builder.mCarrierId;
+            this.mIsNtnOnlyCarrier = builder.mIsNtnOnlyCarrier;
         }
 
         public boolean isDisplaySosMessageSent() {
@@ -1421,8 +1581,12 @@
             return mCarrierId;
         }
 
+        public boolean isNtnOnlyCarrier() {
+            return mIsNtnOnlyCarrier;
+        }
+
         /**
-         * A builder class to create {@link SatelliteProvisionParams} data structure class
+         * A builder class to create {@link SatelliteSosMessageRecommender} data structure class
          */
         public static class Builder {
             private boolean mIsDisplaySosMessageSent = false;
@@ -1434,7 +1598,7 @@
             private boolean mIsSatelliteAllowedInCurrentLocation = false;
             private boolean mIsWifiConnected = false;
             private int mCarrierId = UNKNOWN_CARRIER_ID;
-
+            private boolean mIsNtnOnlyCarrier = false;
 
             /**
              * Sets resultCode value of {@link SatelliteSosMessageRecommender} atom
@@ -1520,6 +1684,15 @@
             }
 
             /**
+             * Sets isNtnOnlyCarrier value of {@link SatelliteSosMessageRecommender} atom
+             * then returns Builder class
+            */
+            public Builder setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier) {
+                this.mIsNtnOnlyCarrier = isNtnOnlyCarrier;
+                return this;
+            }
+
+            /**
              * Returns SosMessageRecommenderParams, which contains whole component of
              * {@link SatelliteSosMessageRecommenderParams} atom
              */
@@ -1542,6 +1715,7 @@
                     + mIsSatelliteAllowedInCurrentLocation
                     + ", isWifiConnected=" + mIsWifiConnected
                     + ", carrierId=" + mCarrierId
+                    + ", isNtnOnlyCarrier=" + mIsNtnOnlyCarrier
                     + ")";
         }
     }
@@ -2283,6 +2457,7 @@
         private final @SatelliteConstants.ConfigDataSource int mConfigDataSource;
         private final int mCarrierId;
         private final int mTriggeringEvent;
+        private final boolean mIsNtnOnlyCarrier;
 
         private SatelliteAccessControllerParams(Builder builder) {
             this.mAccessControlType = builder.mAccessControlType;
@@ -2296,6 +2471,7 @@
             this.mConfigDataSource = builder.mConfigDataSource;
             this.mCarrierId = builder.mCarrierId;
             this.mTriggeringEvent = builder.mTriggeringEvent;
+            this.mIsNtnOnlyCarrier = builder.mIsNtnOnlyCarrier;
         }
 
         public @SatelliteConstants.AccessControlType int getAccessControlType() {
@@ -2342,6 +2518,10 @@
             return mTriggeringEvent;
         }
 
+        public boolean isNtnOnlyCarrier() {
+            return mIsNtnOnlyCarrier;
+        }
+
         /**
          * A builder class to create {@link SatelliteAccessControllerParams} data structure class
          */
@@ -2358,6 +2538,7 @@
             private int mCarrierId = UNKNOWN_CARRIER_ID;
             private @SatelliteConstants.TriggeringEvent int mTriggeringEvent =
                     TRIGGERING_EVENT_UNKNOWN;
+            private boolean mIsNtnOnlyCarrier = false;
 
             /**
              * Sets AccessControlType value of {@link #SatelliteAccessController}
@@ -2433,6 +2614,15 @@
             }
 
             /**
+             * Sets isNtnOnlyCarrier value of {@link SatelliteAccessController} atom
+             * then returns Builder class
+            */
+            public Builder setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier) {
+                this.mIsNtnOnlyCarrier = isNtnOnlyCarrier;
+                return this;
+            }
+
+            /**
              * Returns AccessControllerParams, which contains whole component of
              * {@link #SatelliteAccessController} atom
              */
@@ -2456,6 +2646,7 @@
                     + ", ConfigDataSource=" + mConfigDataSource
                     + ", CarrierId=" + mCarrierId
                     + ", TriggeringEvent=" + mTriggeringEvent
+                    + ", IsNtnOnlyCarrier=" + mIsNtnOnlyCarrier
                     + ")";
         }
     }
@@ -2505,6 +2696,11 @@
                 param.getCountOfSatelliteAllowedStateChangedEvents();
         proto.countOfSuccessfulLocationQueries = param.getCountOfSuccessfulLocationQueries();
         proto.countOfFailedLocationQueries = param.getCountOfFailedLocationQueries();
+        proto.countOfP2PSmsAvailableNotificationShown =
+                param.getCountOfP2PSmsAvailableNotificationShown();
+        proto.countOfP2PSmsAvailableNotificationRemoved =
+                param.getCountOfP2PSmsAvailableNotificationRemoved();
+        proto.isNtnOnlyCarrier = param.isNtnOnlyCarrier();
 
         mAtomsStorage.addSatelliteControllerStats(proto);
     }
@@ -2531,6 +2727,9 @@
                 param.getCountOfSatelliteNotificationDisplayed();
         proto.countOfAutoExitDueToScreenOff = param.getCountOfAutoExitDueToScreenOff();
         proto.countOfAutoExitDueToTnNetwork = param.getCountOfAutoExitDueToTnNetwork();
+        proto.isEmergency = param.getIsEmergency();
+        proto.isNtnOnlyCarrier = param.isNtnOnlyCarrier();
+        proto.maxInactivityDurationSec = param.getMaxInactivityDurationSec();
         mAtomsStorage.addSatelliteSessionStats(proto);
     }
 
@@ -2543,6 +2742,7 @@
         proto.datagramTransferTimeMillis = param.getDatagramTransferTimeMillis();
         proto.isDemoMode = param.getIsDemoMode();
         proto.carrierId = param.getCarrierId();
+        proto.isNtnOnlyCarrier = param.isNtnOnlyCarrier();
         mAtomsStorage.addSatelliteIncomingDatagramStats(proto);
     }
 
@@ -2556,6 +2756,7 @@
         proto.datagramTransferTimeMillis = param.getDatagramTransferTimeMillis();
         proto.isDemoMode = param.getIsDemoMode();
         proto.carrierId = param.getCarrierId();
+        proto.isNtnOnlyCarrier = param.isNtnOnlyCarrier();
         mAtomsStorage.addSatelliteOutgoingDatagramStats(proto);
     }
 
@@ -2567,6 +2768,7 @@
         proto.isProvisionRequest = param.getIsProvisionRequest();
         proto.isCanceled = param.getIsCanceled();
         proto.carrierId = param.getCarrierId();
+        proto.isNtnOnlyCarrier = param.isNtnOnlyCarrier();
         mAtomsStorage.addSatelliteProvisionStats(proto);
     }
 
@@ -2583,6 +2785,7 @@
         proto.isSatelliteAllowedInCurrentLocation = param.isSatelliteAllowedInCurrentLocation();
         proto.isWifiConnected = param.isWifiConnected();
         proto.carrierId = param.getCarrierId();
+        proto.isNtnOnlyCarrier = param.isNtnOnlyCarrier();
         proto.count = 1;
         mAtomsStorage.addSatelliteSosMessageRecommenderStats(proto);
     }
@@ -2662,6 +2865,7 @@
         proto.configDataSource = param.getConfigDataSource();
         proto.carrierId = param.getCarrierId();
         proto.triggeringEvent = param.getTriggeringEvent();
+        proto.isNtnOnlyCarrier = param.isNtnOnlyCarrier();
         mAtomsStorage.addSatelliteAccessControllerStats(proto);
     }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramController.java b/src/java/com/android/internal/telephony/satellite/DatagramController.java
index a816906..acd3fd1 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramController.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramController.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS;
 import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE;
 import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_UNKNOWN;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
@@ -282,7 +283,7 @@
             mSendDatagramTransferState = datagramTransferState;
             mSendPendingCount = sendPendingCount;
             mSendErrorCode = errorCode;
-            notifyDatagramTransferStateChangedToSessionController();
+            notifyDatagramTransferStateChangedToSessionController(mDatagramType);
             mPointingAppController.updateSendDatagramTransferState(mSendSubId, mDatagramType,
                     mSendDatagramTransferState, mSendPendingCount, mSendErrorCode);
             retryPollPendingDatagramsInDemoMode();
@@ -311,21 +312,23 @@
      * @param receivePendingCount The number of datagrams that are currently pending to be received.
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
-    public void updateReceiveStatus(int subId,
+    public void updateReceiveStatus(int subId, @SatelliteManager.DatagramType int datagramType,
             @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState,
             int receivePendingCount, int errorCode) {
         synchronized (mLock) {
             plogd("updateReceiveStatus"
                     + " subId: " + subId
+                    + " datagramType: " + datagramType
                     + " datagramTransferState: " + datagramTransferState
                     + " receivePendingCount: " + receivePendingCount + " errorCode: " + errorCode);
 
             mReceiveSubId = subId;
+            mDatagramType = datagramType;
             mReceiveDatagramTransferState = datagramTransferState;
             mReceivePendingCount = receivePendingCount;
             mReceiveErrorCode = errorCode;
 
-            notifyDatagramTransferStateChangedToSessionController();
+            notifyDatagramTransferStateChangedToSessionController(mDatagramType);
             mPointingAppController.updateReceiveDatagramTransferState(mReceiveSubId,
                     mReceiveDatagramTransferState, mReceivePendingCount, mReceiveErrorCode);
             retryPollPendingDatagramsInDemoMode();
@@ -365,6 +368,24 @@
     }
 
     /**
+     * Notify SMS received.
+     *
+     * @param subId The subId of the subscription used to receive SMS
+     */
+    public void onSmsReceived(int subId) {
+        // To keep exist notification flow, need to call with each state.
+        updateReceiveStatus(subId, SatelliteManager.DATAGRAM_TYPE_SMS,
+                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING,
+                getReceivePendingCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
+        updateReceiveStatus(subId, SatelliteManager.DATAGRAM_TYPE_SMS,
+                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS,
+                getReceivePendingCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
+        updateReceiveStatus(subId, SatelliteManager.DATAGRAM_TYPE_SMS,
+                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                getReceivePendingCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
+    }
+
+    /**
      * Set whether the device is aligned with the satellite.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -399,6 +420,15 @@
                     && mSatelltieModemState == SATELLITE_MODEM_STATE_NOT_CONNECTED) {
                 return false;
             }
+            boolean allowCheckMessageInNotConnected =
+                    mContext.getResources().getBoolean(
+                            R.bool.config_satellite_allow_check_message_in_not_connected);
+            if (datagramType == DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS
+                    && mSatelltieModemState == SATELLITE_MODEM_STATE_NOT_CONNECTED
+                    && allowCheckMessageInNotConnected
+                    && mFeatureFlags.carrierRoamingNbIotNtn()) {
+                return false;
+            }
             if (mSatelltieModemState != SATELLITE_MODEM_STATE_CONNECTED
                     && mSatelltieModemState != SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING) {
                 return true;
@@ -565,14 +595,16 @@
         return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false));
     }
 
-    private void notifyDatagramTransferStateChangedToSessionController() {
+    private void notifyDatagramTransferStateChangedToSessionController(int datagramType) {
         SatelliteSessionController sessionController = SatelliteSessionController.getInstance();
         if (sessionController == null) {
             ploge("notifyDatagramTransferStateChangeToSessionController: SatelliteSessionController"
                     + " is not initialized yet");
         } else {
-            sessionController.onDatagramTransferStateChanged(
-                    mSendDatagramTransferState, mReceiveDatagramTransferState);
+            synchronized (mLock) {
+                sessionController.onDatagramTransferStateChanged(
+                        mSendDatagramTransferState, mReceiveDatagramTransferState, datagramType);
+            }
         }
     }
 
@@ -616,7 +648,7 @@
                     }
                 };
                 pollPendingSatelliteDatagrams(
-                        SatelliteController.getInstance().getHighestPrioritySubscrption(),
+                        SatelliteController.getInstance().getSelectedSatelliteSubId(),
                         internalCallback);
             }
         }
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
index 682123f..d1d8726 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
@@ -16,9 +16,13 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS;
 import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_SMS;
 import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_UNKNOWN;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NETWORK_ERROR;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE;
@@ -40,6 +44,8 @@
 import android.telephony.Rlog;
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatelliteSessionStats;
+import android.util.Log;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -75,6 +81,7 @@
     private static final int EVENT_WAIT_FOR_SIMULATED_POLL_DATAGRAMS_DELAY_TIMED_OUT = 7;
     private static final int CMD_SEND_SMS = 8;
     private static final int EVENT_SEND_SMS_DONE = 9;
+    private static final int EVENT_MT_SMS_POLLING_THROTTLE_TIMED_OUT = 10;
     private static final Long TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE = TimeUnit.SECONDS.toMillis(10);
     @NonNull private static DatagramDispatcher sInstance;
     @NonNull private final Context mContext;
@@ -130,6 +137,18 @@
     private int mLastSendRequestDatagramType = DATAGRAM_TYPE_UNKNOWN;
     @Nullable private PersistentLogger mPersistentLogger = null;
 
+    @GuardedBy("mLock")
+    private int mModemState = SATELLITE_MODEM_STATE_UNKNOWN;
+    @GuardedBy("mLock")
+    private boolean mHasEnteredConnectedState = false;
+    @GuardedBy("mLock")
+    private boolean mShouldPollMtSms = false;
+    @GuardedBy("mLock")
+    private boolean mIsMtSmsPollingThrottled = false;
+    @GuardedBy("mLock")
+    private int mConnectedStateCounter = 0;
+    private long mSmsTransmissionStartTime = 0;
+
     /**
      * Create the DatagramDispatcher singleton instance.
      * @param context The Context to use to create the DatagramDispatcher.
@@ -256,6 +275,7 @@
                 request = (DatagramDispatcherHandlerRequest) msg.obj;
                 SendSatelliteDatagramArgument argument =
                         (SendSatelliteDatagramArgument) request.argument;
+                argument.setDatagramStartTime();
                 onCompleted = obtainMessage(EVENT_SEND_SATELLITE_DATAGRAM_DONE, request);
 
                 synchronized (mLock) {
@@ -385,6 +405,7 @@
                     return;
                 }
 
+                mSmsTransmissionStartTime = System.currentTimeMillis();
                 smsDispatchersController.sendCarrierRoamingNbIotNtnText(pendingRequest);
                 break;
             }
@@ -402,6 +423,16 @@
                 break;
             }
 
+            case EVENT_MT_SMS_POLLING_THROTTLE_TIMED_OUT: {
+                synchronized (mLock) {
+                    mIsMtSmsPollingThrottled = false;
+                    if (allowMtSmsPolling()) {
+                        sendMtSmsPollingMessage();
+                    }
+                }
+                break;
+            }
+
             default:
                 plogw("DatagramDispatcherHandler: unexpected message code: " + msg.what);
                 break;
@@ -454,7 +485,6 @@
                 // Modem can be busy receiving datagrams, so send datagram only when modem is
                 // not busy.
                 mSendingInProgress = true;
-                datagramArgs.setDatagramStartTime();
                 mDatagramController.updateSendStatus(subId, datagramType,
                         SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
                         getPendingMessagesCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
@@ -491,6 +521,9 @@
             mIsAligned = isAligned;
             plogd("setDeviceAlignedWithSatellite: " + mIsAligned);
             if (isAligned && mIsDemoMode) handleEventSatelliteAligned();
+            if (allowMtSmsPolling()) {
+                sendMtSmsPollingMessage();
+            }
         }
     }
 
@@ -598,12 +631,18 @@
                     pendingDatagram.iterator().next().getValue();
             if (mDatagramController.needsWaitingForSatelliteConnected(datagramArg.datagramType)) {
                 plogd("sendPendingDatagrams: wait for satellite connected");
+                mDatagramController.updateSendStatus(datagramArg.subId,
+                        datagramArg.datagramType,
+                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                        getPendingMessagesCount(),
+                        SatelliteManager.SATELLITE_RESULT_SUCCESS);
+                startDatagramWaitForConnectedStateTimer(
+                        datagramArg.datagramType);
                 return;
             }
 
             mSendingInProgress = true;
             // Sets the trigger time for getting pending datagrams
-            datagramArg.setDatagramStartTime();
             mDatagramController.updateSendStatus(datagramArg.subId, datagramArg.datagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
                     getPendingMessagesCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
@@ -717,8 +756,25 @@
         msg.sendToTarget();
     }
 
+    private void reportSendSmsCompleted(@NonNull PendingRequest pendingRequest,
+            @SatelliteManager.SatelliteResult int resultCode) {
+        int datagramType = pendingRequest.isMtSmsPolling
+                ? DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS : DATAGRAM_TYPE_SMS;
+        if (resultCode == SATELLITE_RESULT_SUCCESS) {
+            long smsTransmissionTime = mSmsTransmissionStartTime > 0
+                    ? (System.currentTimeMillis() - mSmsTransmissionStartTime) : 0;
+            mSessionMetricsStats.addCountOfSuccessfulOutgoingDatagram(
+                    datagramType, smsTransmissionTime);
+        } else {
+            mSessionMetricsStats.addCountOfFailedOutgoingDatagram(
+                    datagramType, resultCode);
+        }
+    }
+
     private void reportSendDatagramCompleted(@NonNull SendSatelliteDatagramArgument argument,
             @NonNull @SatelliteManager.SatelliteResult int resultCode) {
+        long datagramTransmissionTime = argument.datagramStartTime > 0
+                ? (System.currentTimeMillis() - argument.datagramStartTime) : 0;
         SatelliteStats.getInstance().onSatelliteOutgoingDatagramMetrics(
                 new SatelliteStats.SatelliteOutgoingDatagramParams.Builder()
                         .setDatagramType(argument.datagramType)
@@ -726,15 +782,16 @@
                         .setDatagramSizeBytes(argument.getDatagramRoundedSizeBytes())
                         /* In case pending datagram has not been attempted to send to modem
                         interface. transfer time will be 0. */
-                        .setDatagramTransferTimeMillis(argument.datagramStartTime > 0
-                                ? (System.currentTimeMillis() - argument.datagramStartTime) : 0)
+                        .setDatagramTransferTimeMillis(datagramTransmissionTime)
                         .setIsDemoMode(mIsDemoMode)
                         .setCarrierId(SatelliteController.getInstance().getSatelliteCarrierId())
+                        .setIsNtnOnlyCarrier(SatelliteController.getInstance().isNtnOnlyCarrier())
                         .build());
         if (resultCode == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
             mControllerMetricsStats.reportOutgoingDatagramSuccessCount(argument.datagramType,
                     mIsDemoMode);
-            mSessionMetricsStats.addCountOfSuccessfulOutgoingDatagram(argument.datagramType);
+            mSessionMetricsStats.addCountOfSuccessfulOutgoingDatagram(argument.datagramType,
+                    datagramTransmissionTime);
         } else {
             mControllerMetricsStats.reportOutgoingDatagramFailCount(argument.datagramType,
                     mIsDemoMode);
@@ -759,6 +816,7 @@
      */
     public void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state) {
         synchronized (mLock) {
+            mModemState = state;
             if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF
                     || state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) {
                 plogd("onSatelliteModemStateChanged: cleaning up resources");
@@ -767,21 +825,45 @@
                 sendPendingMessages();
             }
 
-            if (state == SATELLITE_MODEM_STATE_CONNECTED
-                    && isDatagramWaitForConnectedStateTimerStarted()) {
-                stopDatagramWaitForConnectedStateTimer();
-                sendPendingMessages();
+            if (state == SATELLITE_MODEM_STATE_CONNECTED) {
+                mHasEnteredConnectedState = true;
+
+                mConnectedStateCounter++;
+                if (isFirstConnected()) {
+                    mShouldPollMtSms = shouldPollMtSms();
+                }
+
+                if (isDatagramWaitForConnectedStateTimerStarted()) {
+                    stopDatagramWaitForConnectedStateTimer();
+                    sendPendingMessages();
+                }
+            }
+
+            if (state == SATELLITE_MODEM_STATE_NOT_CONNECTED) {
+                if (mHasEnteredConnectedState) {
+                    mHasEnteredConnectedState = false;
+                    mShouldPollMtSms = shouldPollMtSms();
+                }
+            }
+
+            if (allowMtSmsPolling()) {
+                sendMtSmsPollingMessage();
             }
         }
     }
 
+    /** Returns true if this is the first time the satellite modem is connected. */
+    private boolean isFirstConnected() {
+        return mConnectedStateCounter == 1;
+    }
+
     @GuardedBy("mLock")
     private void cleanUpResources() {
         plogd("cleanUpResources");
         mSendingInProgress = false;
         mIsEmergencyCommunicationEstablished = false;
 
-        int subId = SatelliteController.getInstance().getHighestPrioritySubscrption();
+        int subId = SatelliteController.getInstance().getSelectedSatelliteSubId();
         if (getPendingMessagesCount() > 0) {
             mDatagramController.updateSendStatus(subId,
                     mLastSendRequestDatagramType,
@@ -803,6 +885,11 @@
         mSendSatelliteDatagramRequest = null;
         mIsAligned = false;
         mLastSendRequestDatagramType = DATAGRAM_TYPE_UNKNOWN;
+        mModemState = SATELLITE_MODEM_STATE_UNKNOWN;
+        mHasEnteredConnectedState = false;
+        mShouldPollMtSms = false;
+        mConnectedStateCounter = 0;
+        stopMtSmsPollingThrottle();
     }
 
     /** @return {@code true} if already sent an emergency datagram during a session. */
@@ -867,7 +954,7 @@
             @SatelliteManager.DatagramType int datagramType) {
         plogw("Timed out to wait for satellite connected before sending datagrams");
         synchronized (mLock) {
-            int subId = SatelliteController.getInstance().getHighestPrioritySubscrption();
+            int subId = SatelliteController.getInstance().getSelectedSatelliteSubId();
             // Update send status
             mDatagramController.updateSendStatus(subId,
                     datagramType,
@@ -1051,22 +1138,17 @@
      *                    carrier roaming nb iot ntn SMS.
      */
     public void sendSms(@NonNull PendingRequest pendingSms) {
-        Phone satellitePhone = SatelliteController.getInstance().getSatellitePhone();
-        if (satellitePhone == null) {
-            ploge("sendSms: satellitePhone is null.");
-            return;
-        }
-
         SatelliteController.getInstance().startPointingUI();
 
-        int subId = satellitePhone.getSubId();
-        long messageId = pendingSms.messageId;
+        int subId = SatelliteController.getInstance().getSelectedSatelliteSubId();
+        long messageId = pendingSms.uniqueMessageId;
         plogd("sendSms: subId=" + subId + " messageId:" + messageId);
 
         synchronized (mLock) {
             // Add SMS to pending list
             mPendingSmsMap.put(messageId, pendingSms);
-            int datagramType = SatelliteManager.DATAGRAM_TYPE_SMS;
+            int datagramType = pendingSms.isMtSmsPolling ?
+                    DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS : DATAGRAM_TYPE_SMS;
             mLastSendRequestDatagramType = datagramType;
 
             if (mDatagramController.needsWaitingForSatelliteConnected(datagramType)) {
@@ -1098,27 +1180,30 @@
             return;
         }
 
-        Phone satellitePhone = SatelliteController.getInstance().getSatellitePhone();
-        if (satellitePhone == null) {
-            ploge("sendPendingSms: satellitePhone is null.");
-            return;
-        }
-        int subId = satellitePhone.getSubId();
-
+        int subId = SatelliteController.getInstance().getSelectedSatelliteSubId();
         Set<Entry<Long, PendingRequest>> pendingSms = null;
         if (!mSendingInProgress) {
             pendingSms = mPendingSmsMap.entrySet();
         }
 
         if (pendingSms != null && pendingSms.iterator().hasNext()) {
+            PendingRequest pendingRequest = pendingSms.iterator().next().getValue();
+            int datagramType = pendingRequest.isMtSmsPolling
+                    ? DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS : DATAGRAM_TYPE_SMS;
             if (mDatagramController.needsWaitingForSatelliteConnected(DATAGRAM_TYPE_SMS)) {
                 plogd("sendPendingSms: wait for satellite connected");
+                mDatagramController.updateSendStatus(subId,
+                        datagramType,
+                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                        getPendingMessagesCount(),
+                        SatelliteManager.SATELLITE_RESULT_SUCCESS);
+                startDatagramWaitForConnectedStateTimer(datagramType);
                 return;
             }
 
             mSendingInProgress = true;
-            PendingRequest pendingRequest = pendingSms.iterator().next().getValue();
-            mDatagramController.updateSendStatus(subId, DATAGRAM_TYPE_SMS,
+            mDatagramController.updateSendStatus(subId,
+                    datagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
                     getPendingMessagesCount(), SATELLITE_RESULT_SUCCESS);
             sendMessage(obtainMessage(CMD_SEND_SMS, pendingRequest));
@@ -1173,6 +1258,7 @@
             PendingRequest pendingRequest = entry.getValue();
             smsDispatchersController.onSendCarrierRoamingNbIotNtnTextError(
                     pendingRequest, errorCode);
+            reportSendSmsCompleted(pendingRequest, errorCode);
         }
 
         // Clear pending text map
@@ -1181,21 +1267,36 @@
 
     private void handleEventSendSmsDone(int subId, long messageId, boolean success) {
         synchronized (mLock) {
+            PendingRequest pendingSms = mPendingSmsMap.remove(messageId);
+            if (pendingSms == null) {
+                // Just return, the SMS is not sent by DatagramDispatcher such as Data SMS
+                plogd("handleEventSendSmsDone there is no pendingSms for messageId=" + messageId);
+                return;
+            }
+
             mSendingInProgress = false;
-            mPendingSmsMap.remove(messageId);
-            int datagramType = DATAGRAM_TYPE_SMS;
+            int datagramType = pendingSms.isMtSmsPolling
+                    ? DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS  : DATAGRAM_TYPE_SMS;
 
             plogd("handleEventSendSmsDone subId=" + subId + " messageId=" + messageId
-                    + " success=" + success);
+                    + " success=" + success + " datagramType=" + datagramType);
+
             if (success) {
-                // Update send status for current datagram
+                // Update send status
                 mDatagramController.updateSendStatus(subId, datagramType,
                         SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS,
                         getPendingMessagesCount(), SATELLITE_RESULT_SUCCESS);
+                reportSendSmsCompleted(pendingSms, SATELLITE_RESULT_SUCCESS);
+                if (datagramType == DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS) {
+                    startMtSmsPollingThrottle();
+                    mShouldPollMtSms = false;
+                }
             } else {
+                // Update send status
                 mDatagramController.updateSendStatus(subId, datagramType,
                         SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
                         getPendingMessagesCount(), SATELLITE_RESULT_NETWORK_ERROR);
+                reportSendSmsCompleted(pendingSms, SATELLITE_RESULT_NETWORK_ERROR);
             }
 
             if (getPendingMessagesCount() > 0) {
@@ -1208,6 +1309,96 @@
         }
     }
 
+    private boolean isEnabledMtSmsPolling() {
+        return mContext.getResources().getBoolean(R.bool.config_enabled_mt_sms_polling);
+    }
+
+    private long getMtSmsPollingThrottleMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_mt_sms_polling_throttle_millis);
+    }
+
+    private boolean shouldPollMtSms() {
+        SatelliteController satelliteController = SatelliteController.getInstance();
+        Phone satellitePhone = satelliteController.getSatellitePhone();
+        return isEnabledMtSmsPolling()
+                && satelliteController.shouldSendSmsToDatagramDispatcher(satellitePhone);
+    }
+
+    @GuardedBy("mLock")
+    private void sendMtSmsPollingMessage() {
+        if (!mShouldPollMtSms) {
+            return;
+        }
+
+        plogd("sendMtSmsPollingMessage");
+        if (!allowCheckMessageInNotConnected()) {
+            mShouldPollMtSms = false;
+        }
+
+        for (Entry<Long, PendingRequest> entry : mPendingSmsMap.entrySet()) {
+            PendingRequest pendingRequest = entry.getValue();
+            if (pendingRequest.isMtSmsPolling) {
+                plogd("sendMtSmsPollingMessage: mPendingSmsMap already has the polling message.");
+                return;
+            }
+        }
+
+        Phone satellitePhone = SatelliteController.getInstance().getSatellitePhone();
+        if (satellitePhone == null) {
+            ploge("sendMtSmsPollingMessage: satellitePhone is null.");
+            return;
+        }
+
+        SmsDispatchersController smsDispatchersController =
+                satellitePhone.getSmsDispatchersController();
+        if (smsDispatchersController == null) {
+            ploge("sendMtSmsPollingMessage: smsDispatchersController is null.");
+            return;
+        }
+
+        smsDispatchersController.sendMtSmsPollingMessage();
+    }
+
+    @GuardedBy("mLock")
+    private void startMtSmsPollingThrottle() {
+        plogd("startMtSmsPollingThrottle");
+        mIsMtSmsPollingThrottled = true;
+        sendMessageDelayed(obtainMessage(EVENT_MT_SMS_POLLING_THROTTLE_TIMED_OUT),
+                getMtSmsPollingThrottleMillis());
+    }
+
+    @GuardedBy("mLock")
+    private void stopMtSmsPollingThrottle() {
+        mIsMtSmsPollingThrottled = false;
+        removeMessages(EVENT_MT_SMS_POLLING_THROTTLE_TIMED_OUT);
+    }
+
+    @GuardedBy("mLock")
+    private boolean allowMtSmsPolling() {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) return false;
+
+        if (mIsMtSmsPollingThrottled) return false;
+
+        if (!mIsAligned) return false;
+
+        boolean isModemStateConnectedOrTransferring =
+                mModemState == SATELLITE_MODEM_STATE_CONNECTED
+                        || mModemState == SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
+        if (!isModemStateConnectedOrTransferring && !allowCheckMessageInNotConnected()) {
+            plogd("EVENT_MT_SMS_POLLING_THROTTLE_TIMED_OUT:"
+                    + " allow_check_message_in_not_connected is disabled");
+            return false;
+        }
+
+        return true;
+    }
+
+    private boolean allowCheckMessageInNotConnected() {
+        return mContext.getResources()
+                .getBoolean(R.bool.config_satellite_allow_check_message_in_not_connected);
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
@@ -1251,4 +1442,37 @@
             mPersistentLogger.error(TAG, log);
         }
     }
+
+    public void updateSessionStatsWithPendingUserMsgCount(SatelliteSessionStats datagramStats) {
+        synchronized (mLock) {
+            Log.d("SessionMetricsStats1",
+                    " mPendingEmergencyDatagramsMap size = "
+                            + mPendingEmergencyDatagramsMap.size());
+            Log.d("SessionMetricsStats1", " mPendingNonEmergencyDatagramsMap size = "
+                    + mPendingNonEmergencyDatagramsMap.size());
+            Log.d("SessionMetricsStats1", " mPendingSmsMap size = "
+                    + mPendingSmsMap.size());
+            for (Entry<Long, SendSatelliteDatagramArgument> entry :
+                    mPendingEmergencyDatagramsMap.entrySet()) {
+                SendSatelliteDatagramArgument argument = entry.getValue();
+                Log.d("SessionMetricsStats1", "DataGramType1 =  "
+                        + argument.datagramType);
+                datagramStats.updateCountOfUserMessagesInQueueToBeSent(argument.datagramType);
+            }
+            for (Entry<Long, SendSatelliteDatagramArgument> entry :
+                    mPendingNonEmergencyDatagramsMap.entrySet()) {
+                SendSatelliteDatagramArgument argument = entry.getValue();
+                Log.d("SessionMetricsStats1", "DataGramType2 =  "
+                        + argument.datagramType);
+                datagramStats.updateCountOfUserMessagesInQueueToBeSent(argument.datagramType);
+            }
+            for (Entry<Long, PendingRequest> entry : mPendingSmsMap.entrySet()) {
+                PendingRequest pendingRequest = entry.getValue();
+                int datagramType = pendingRequest.isMtSmsPolling
+                        ? DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS : DATAGRAM_TYPE_SMS;
+                Log.d("SessionMetricsStats1", "DataGramType3 =  " + datagramType);
+                datagramStats.updateCountOfUserMessagesInQueueToBeSent(datagramType);
+            }
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
index 5a89c40..c01f10d 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
@@ -352,10 +352,12 @@
 
                     if (pendingCount <= 0 && satelliteDatagram == null) {
                         sInstance.mDatagramController.updateReceiveStatus(mSubId,
+                                SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
                                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE,
                                 pendingCount, SatelliteManager.SATELLITE_RESULT_SUCCESS);
                     } else if (satelliteDatagram != null) {
                         sInstance.mDatagramController.updateReceiveStatus(mSubId,
+                                SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
                                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS,
                                 pendingCount, SatelliteManager.SATELLITE_RESULT_SUCCESS);
 
@@ -374,8 +376,13 @@
                         });
                     }
 
+                    // Send the captured data about incoming datagram to metric
+                    sInstance.reportMetrics(satelliteDatagram,
+                            SatelliteManager.SATELLITE_RESULT_SUCCESS);
+
                     if (pendingCount <= 0) {
                         sInstance.mDatagramController.updateReceiveStatus(mSubId,
+                                SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
                                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
                                 pendingCount, SatelliteManager.SATELLITE_RESULT_SUCCESS);
                     } else {
@@ -390,10 +397,6 @@
                                 internalCallback::accept);
                         sInstance.pollPendingSatelliteDatagramsInternal(mSubId, callback);
                     }
-
-                    // Send the captured data about incoming datagram to metric
-                    sInstance.reportMetrics(satelliteDatagram,
-                            SatelliteManager.SATELLITE_RESULT_SUCCESS);
                     break;
                 }
 
@@ -473,10 +476,12 @@
                 plogd("EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE error: " + error);
                 if (error != SatelliteManager.SATELLITE_RESULT_SUCCESS) {
                     mDatagramController.updateReceiveStatus(request.subId,
+                            SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
                             SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
                             mDatagramController.getReceivePendingCount(), error);
 
                     mDatagramController.updateReceiveStatus(request.subId,
+                            SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
                             SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
                             mDatagramController.getReceivePendingCount(),
                             SatelliteManager.SATELLITE_RESULT_SUCCESS);
@@ -615,6 +620,7 @@
                 mPendingPollSatelliteDatagramsRequest = new DatagramReceiverHandlerRequest(
                         callback, SatelliteServiceUtils.getPhone(), subId);
                 mDatagramController.updateReceiveStatus(subId,
+                        SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
                         SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
                         mDatagramController.getReceivePendingCount(),
                         SatelliteManager.SATELLITE_RESULT_SUCCESS);
@@ -624,6 +630,7 @@
         }
 
         mDatagramController.updateReceiveStatus(subId,
+                SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING,
                 mDatagramController.getReceivePendingCount(),
                 SatelliteManager.SATELLITE_RESULT_SUCCESS);
@@ -696,14 +703,16 @@
             stopDatagramWaitForConnectedStateTimer();
         }
 
-        int subId = SatelliteController.getInstance().getHighestPrioritySubscrption();
+        int subId = SatelliteController.getInstance().getSelectedSatelliteSubId();
         if (mDatagramController.isReceivingDatagrams()) {
             mDatagramController.updateReceiveStatus(subId,
+                    SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
                     mDatagramController.getReceivePendingCount(),
                     SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
         }
         mDatagramController.updateReceiveStatus(subId,
+                SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, 0,
                 SatelliteManager.SATELLITE_RESULT_SUCCESS);
         cleanupDemoModeResources();
@@ -739,8 +748,8 @@
                         (int) (Math.round((double) sizeBytes / ROUNDING_UNIT) * ROUNDING_UNIT);
             }
             datagramTransferTime = (System.currentTimeMillis() - mDatagramTransferStartTime);
-            mDatagramTransferStartTime = 0;
         }
+        mDatagramTransferStartTime = 0;
 
         SatelliteStats.getInstance().onSatelliteIncomingDatagramMetrics(
                 new SatelliteStats.SatelliteIncomingDatagramParams.Builder()
@@ -749,6 +758,7 @@
                         .setDatagramTransferTimeMillis(datagramTransferTime)
                         .setIsDemoMode(mIsDemoMode)
                         .setCarrierId(SatelliteController.getInstance().getSatelliteCarrierId())
+                        .setIsNtnOnlyCarrier(SatelliteController.getInstance().isNtnOnlyCarrier())
                         .build());
 
         mControllerMetricsStats.reportIncomingDatagramCount(resultCode, mIsDemoMode);
@@ -857,11 +867,13 @@
 
             plogw("Timed out to wait for satellite connected before polling datagrams");
             mDatagramController.updateReceiveStatus(mPendingPollSatelliteDatagramsRequest.subId,
+                    SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
                     mDatagramController.getReceivePendingCount(),
                     SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
 
             mDatagramController.updateReceiveStatus(mPendingPollSatelliteDatagramsRequest.subId,
+                    SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
                     mDatagramController.getReceivePendingCount(),
                     SatelliteManager.SATELLITE_RESULT_SUCCESS);
diff --git a/src/java/com/android/internal/telephony/satellite/DemoSimulator.java b/src/java/com/android/internal/telephony/satellite/DemoSimulator.java
index 7b64c61..d908597 100644
--- a/src/java/com/android/internal/telephony/satellite/DemoSimulator.java
+++ b/src/java/com/android/internal/telephony/satellite/DemoSimulator.java
@@ -287,12 +287,12 @@
      *                             and {@code false} to disable
      * @param errorCallback The callback to receive the error code result of the operation.
      */
-    public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled,
+    public void enableTerrestrialNetworkScanWhileSatelliteModeIsOn(boolean enabled,
             @NonNull IIntegerConsumer errorCallback) {
         try {
             errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS);
         } catch (RemoteException e) {
-            loge("enableCellularModemWhileSatelliteModeIsOn: RemoteException " + e);
+            loge("enableTerrestrialNetworkScanWhileSatelliteModeIsOn: RemoteException " + e);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java b/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java
index dfc7919..4b53178 100644
--- a/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java
+++ b/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java
@@ -52,7 +52,8 @@
                 logd("Registered to satellite PLMN " + satellitePlmn);
                 networkRegistrationInfo.setIsNonTerrestrialNetwork(true);
                 networkRegistrationInfo.setAvailableServices(
-                        satelliteController.getSupportedSatelliteServices(subId, satellitePlmn));
+                        satelliteController.getSupportedSatelliteServicesForPlmn(
+                                subId, satellitePlmn));
                 break;
             }
         }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 8ae6cf4..d455863 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -16,9 +16,13 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
 import static android.provider.Settings.ACTION_SATELLITE_SETTING;
+import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC;
 import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_MANUAL;
 import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_TYPE;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY;
@@ -26,8 +30,11 @@
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE;
 import static android.telephony.CarrierConfigManager.KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT;
 import static android.telephony.CarrierConfigManager.KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL;
+import static android.telephony.CarrierConfigManager.KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT;
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_DATA_SUPPORT_MODE_INT;
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_DISPLAY_NAME_STRING;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ESOS_SUPPORTED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_NIDD_APN_NAME_STRING;
@@ -36,6 +43,8 @@
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL;
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE_BYTES_INT;
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY;
 import static android.telephony.SubscriptionManager.SATELLITE_ATTACH_ENABLED_FOR_CARRIER;
 import static android.telephony.SubscriptionManager.SATELLITE_ENTITLEMENT_STATUS;
 import static android.telephony.SubscriptionManager.isValidSubscriptionId;
@@ -49,6 +58,7 @@
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_INVALID_ARGUMENTS;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_ERROR;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NO_VALID_SATELLITE_SUBSCRIPTION;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
@@ -58,6 +68,7 @@
 import android.annotation.ArrayRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AlertDialog;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
@@ -71,9 +82,13 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.hardware.devicestate.DeviceState;
+import android.hardware.devicestate.DeviceStateManager;
 import android.net.Uri;
 import android.net.wifi.WifiManager;
 import android.nfc.NfcAdapter;
@@ -89,7 +104,6 @@
 import android.os.ICancellationSignal;
 import android.os.Looper;
 import android.os.Message;
-import android.os.OutcomeReceiver;
 import android.os.PersistableBundle;
 import android.os.Registrant;
 import android.os.RegistrantList;
@@ -103,6 +117,7 @@
 import android.provider.Telephony;
 import android.telecom.TelecomManager;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.AnomalyReporter;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DropBoxManagerLoggerBackend;
 import android.telephony.NetworkRegistrationInfo;
@@ -112,6 +127,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
 import android.telephony.satellite.INtnSignalStrengthCallback;
 import android.telephony.satellite.ISatelliteCapabilitiesCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
@@ -119,20 +135,25 @@
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteSupportedStateCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.ISelectedNbIotSatelliteSubscriptionCallback;
 import android.telephony.satellite.NtnSignalStrength;
+import android.telephony.satellite.SatelliteAccessConfiguration;
 import android.telephony.satellite.SatelliteCapabilities;
+import android.telephony.satellite.SatelliteCommunicationAllowedStateCallback;
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.SatelliteModemEnableRequestAttributes;
 import android.telephony.satellite.SatelliteSubscriberInfo;
 import android.telephony.satellite.SatelliteSubscriberProvisionStatus;
 import android.telephony.satellite.SatelliteSubscriptionInfo;
+import android.telephony.satellite.SystemSelectionSpecifier;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.uwb.UwbManager;
+import android.view.WindowManager;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -154,6 +175,7 @@
 import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.FunctionalUtils;
 
@@ -166,6 +188,8 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.TreeMap;
+import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
@@ -200,10 +224,16 @@
     public static final int TIMEOUT_TYPE_DEMO_POINTING_ALIGNED_DURATION_MILLIS = 2;
     /** This is used by CTS to override demo pointing not aligned duration. */
     public static final int TIMEOUT_TYPE_DEMO_POINTING_NOT_ALIGNED_DURATION_MILLIS = 3;
-
+    /** This is used by CTS to override evaluate esos profiles prioritization duration. */
+    public static final int TIMEOUT_TYPE_EVALUATE_ESOS_PROFILES_PRIORITIZATION_DURATION_MILLIS = 4;
     /** Key used to read/write OEM-enabled satellite provision status in shared preferences. */
     private static final String OEM_ENABLED_SATELLITE_PROVISION_STATUS_KEY =
             "oem_enabled_satellite_provision_status_key";
+    /** Key used to read/write default messages application NTN SMS support
+     * in shared preferences. */
+    @VisibleForTesting(visibility =  VisibleForTesting.Visibility.PRIVATE)
+    public static final String NTN_SMS_SUPPORTED_BY_MESSAGES_APP_KEY =
+            "ntn_sms_supported_by_messages_app_key";
 
     public static final long DEFAULT_CARRIER_EMERGENCY_CALL_WAIT_FOR_CONNECTION_TIMEOUT_MILLIS =
             TimeUnit.SECONDS.toMillis(30);
@@ -212,6 +242,14 @@
     private static final long WAIT_FOR_REPORT_ENTITLED_MERTICS_TIMEOUT_MILLIS =
             TimeUnit.HOURS.toMillis(23);
 
+    /**
+     * Delay SatelliteEnable request when network selection auto. current RIL not verified to
+     * response right after network selection auto changed. Some RIL has delay for waiting in-svc
+     * with Automatic selection request.
+     */
+    private static final long DELAY_WAITING_SET_NETWORK_SELECTION_AUTO_MILLIS =
+            TimeUnit.SECONDS.toMillis(1);
+
     /** Message codes used in handleMessage() */
     //TODO: Move the Commands and events related to position updates to PointingAppController
     private static final int CMD_START_SATELLITE_TRANSMISSION_UPDATES = 1;
@@ -256,7 +294,6 @@
     private static final int EVENT_UPDATE_PROVISION_SATELLITE_TOKEN_DONE = 45;
     private static final int EVENT_NOTIFY_NTN_ELIGIBILITY_HYSTERESIS_TIMED_OUT = 46;
     private static final int EVENT_WIFI_CONNECTIVITY_STATE_CHANGED = 47;
-    private static final int EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT = 48;
     protected static final int EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT = 49;
     private static final int CMD_UPDATE_SATELLITE_ENABLE_ATTRIBUTES = 50;
     private static final int EVENT_UPDATE_SATELLITE_ENABLE_ATTRIBUTES_DONE = 51;
@@ -264,6 +301,13 @@
             EVENT_WAIT_FOR_UPDATE_SATELLITE_ENABLE_ATTRIBUTES_RESPONSE_TIMED_OUT = 52;
     private static final int EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT = 53;
     protected static final int EVENT_SATELLITE_REGISTRATION_FAILURE = 54;
+    private static final int EVENT_TERRESTRIAL_NETWORK_AVAILABLE_CHANGED = 55;
+    private static final int EVENT_SET_NETWORK_SELECTION_AUTO_DONE = 56;
+    private static final int EVENT_SIGNAL_STRENGTH_CHANGED = 57;
+    private static final int CMD_UPDATE_SYSTEM_SELECTION_CHANNELS = 58;
+    private static final int EVENT_UPDATE_SYSTEM_SELECTION_CHANNELS_DONE = 59;
+    private static final int EVENT_SELECTED_NB_IOT_SATELLITE_SUBSCRIPTION_CHANGED = 60;
+    private static final int CMD_EVALUATE_CARRIER_ROAMING_NTN_ELIGIBILITY_CHANGE = 61;
 
     @NonNull private static SatelliteController sInstance;
     @NonNull private final Context mContext;
@@ -276,6 +320,7 @@
     @NonNull private final ProvisionMetricsStats mProvisionMetricsStats;
     @NonNull private SessionMetricsStats mSessionMetricsStats;
     @NonNull private CarrierRoamingSatelliteControllerStats mCarrierRoamingSatelliteControllerStats;
+
     @NonNull private final SubscriptionManagerService mSubscriptionManagerService;
     @NonNull private final TelephonyCountryDetector mCountryDetector;
     @NonNull private final TelecomManager mTelecomManager;
@@ -346,6 +391,10 @@
             new AtomicBoolean(false);
     private final AtomicBoolean mRegisteredForSatelliteRegistrationFailure =
             new AtomicBoolean(false);
+    private final AtomicBoolean mRegisteredForTerrestrialNetworkAvailableChanged =
+            new AtomicBoolean(false);
+    private final AtomicBoolean mRegisteredForSatelliteCommunicationAllowedStateChanged =
+        new AtomicBoolean(false);
     /**
      * Map key: subId, value: callback to get error code of the provision request.
      */
@@ -380,9 +429,22 @@
      */
     private final ConcurrentHashMap<IBinder, ISatelliteModemStateCallback>
             mSatelliteRegistrationFailureListeners = new ConcurrentHashMap<>();
-    private final Object mIsSatelliteSupportedLock = new Object();
+    /**
+     * Map key: binder of the callback, value: callback to receive terrestrial network
+     * available changed
+     */
+    private final ConcurrentHashMap<IBinder, ISatelliteModemStateCallback>
+            mTerrestrialNetworkAvailableChangedListeners = new ConcurrentHashMap<>();
+    /**
+     * Map key: binder of the callback, value: callback to receive selected NB IOT satellite
+     * subscription changed
+     */
+    private final ConcurrentHashMap<IBinder, ISelectedNbIotSatelliteSubscriptionCallback>
+            mSelectedNbIotSatelliteSubscriptionChangedListeners = new ConcurrentHashMap<>();
+
+    protected final Object mIsSatelliteSupportedLock = new Object();
     @GuardedBy("mIsSatelliteSupportedLock")
-    private Boolean mIsSatelliteSupported = null;
+    protected Boolean mIsSatelliteSupported = null;
     private boolean mIsDemoModeEnabled = false;
     private boolean mIsEmergency = false;
     private final Object mIsSatelliteEnabledLock = new Object();
@@ -396,11 +458,11 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected boolean mRadioOffRequested = false;
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    protected final Object mSatelliteViaOemProvisionLock = new Object();
+    protected final Object mDeviceProvisionLock = new Object();
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    @GuardedBy("mSatelliteViaOemProvisionLock")
-    protected Boolean mIsSatelliteViaOemProvisioned = null;
-    @GuardedBy("mSatelliteViaOemProvisionLock")
+    @GuardedBy("mDeviceProvisionLock")
+    protected Boolean mIsDeviceProvisioned = null;
+    @GuardedBy("mDeviceProvisionLock")
     private Boolean mOverriddenIsSatelliteViaOemProvisioned = null;
     private final Object mSatelliteCapabilitiesLock = new Object();
     @GuardedBy("mSatelliteCapabilitiesLock")
@@ -438,6 +500,13 @@
      * {@code true} for enabled and {@code false} for disabled. */
     @NonNull private final Map<Integer, Boolean> mIsSatelliteAttachEnabledForCarrierArrayPerSub =
             new HashMap<>();
+    /** Key: subId, value: (key: Regional satellite config Id string, value: Integer
+     * arrays of earfcns in the corresponding regions.)
+     */
+    @GuardedBy("mRegionalSatelliteEarfcnsLock")
+    @NonNull private final Map<Integer, Map<String, Set<Integer>>>
+            mRegionalSatelliteEarfcns = new HashMap<>();
+    @NonNull private final Object mRegionalSatelliteEarfcnsLock = new Object();
     @NonNull private final FeatureFlags mFeatureFlags;
     @NonNull private final Object mSatelliteConnectedLock = new Object();
     /** Key: Subscription ID; Value: Last satellite connected time */
@@ -465,8 +534,6 @@
     @GuardedBy("mSatellitePhoneLock")
     private Boolean mLastNotifiedNtnEligibility = null;
     @GuardedBy("mSatellitePhoneLock")
-    private boolean mNtnEligibilityHysteresisTimedOut = false;
-    @GuardedBy("mSatellitePhoneLock")
     private boolean mCheckingAccessRestrictionInProgress = false;
 
     @GuardedBy("mSatelliteConnectedLock")
@@ -481,6 +548,10 @@
     @NonNull private final Map<Integer, List<Integer>>
             mSatModeCapabilitiesForCarrierRoaming = new HashMap<>();
 
+    @GuardedBy("mSatelliteConnectedLock")
+    private SparseArray<NtnSignalStrength> mLastNotifiedCarrierRoamingNtnSignalStrength =
+            new SparseArray<>();
+
     /**
      * This is used for testing only. When mEnforcedEmergencyCallToSatelliteHandoverType is valid,
      * Telephony will ignore the IMS registration status and cellular availability, and always send
@@ -510,6 +581,21 @@
      * carrierPlmnList. */
     @GuardedBy("mSupportedSatelliteServicesLock")
     private final SparseArray<List<String>> mMergedPlmnListPerCarrier = new SparseArray<>();
+    /** Key Subscription ID, value : map to plmn info with related data plan. */
+    @GuardedBy("mSupportedSatelliteServicesLock")
+    SparseArray<Map<String, Integer>> mEntitlementDataPlanMapPerCarrier = new SparseArray<>();
+    /** Key Subscription ID, value : map to plmn info with related service type. */
+    @GuardedBy("mSupportedSatelliteServicesLock")
+    SparseArray<Map<String, List<Integer>>> mEntitlementServiceTypeMapPerCarrier =
+            new SparseArray<>();
+    /** Key Subscription ID, value : map to plmn info with related service policy for data service */
+    @GuardedBy("mSupportedSatelliteServicesLock")
+    SparseArray<Map<String, Integer>> mEntitlementDataServicePolicyMapPerCarrier =
+            new SparseArray<>();
+    /** Key Subscription ID, value : map to plmn info with related service policy for voice service */
+    @GuardedBy("mSupportedSatelliteServicesLock")
+    SparseArray<Map<String, Integer>> mEntitlementVoiceServicePolicyMapPerCarrier =
+            new SparseArray<>();
     private static AtomicLong sNextSatelliteEnableRequestId = new AtomicLong(0);
     // key : subscriberId, value : provisioned or not.
     @GuardedBy("mSatelliteTokenProvisionedLock")
@@ -521,7 +607,16 @@
     // key : priority, low value is high, value : List<SubscriptionInfo>
     @GuardedBy("mSatelliteTokenProvisionedLock")
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    protected Map<Integer, List<SubscriptionInfo>> mSubsInfoListPerPriority = new HashMap<>();
+    protected TreeMap<Integer, List<SubscriptionInfo>> mSubsInfoListPerPriority = new TreeMap<>();
+    // List of subscriber information and status at the time of last evaluation
+    @GuardedBy("mSatelliteTokenProvisionedLock")
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    private List<SatelliteSubscriberProvisionStatus> mLastEvaluatedSubscriberProvisionStatus =
+            new ArrayList<>();
+    // The ID of the satellite subscription that has highest priority and is provisioned.
+    @GuardedBy("mSatelliteTokenProvisionedLock")
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected int mSelectedSatelliteSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     // The last ICC ID that framework configured to modem.
     @GuardedBy("mSatelliteTokenProvisionedLock")
     private String mLastConfiguredIccId;
@@ -530,11 +625,13 @@
     private long mWaitTimeForSatelliteEnablingResponse;
     private long mDemoPointingAlignedDurationMillis;
     private long mDemoPointingNotAlignedDurationMillis;
+    private long mEvaluateEsosProfilesPrioritizationDurationMillis;
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private long mLastEmergencyCallTime;
     private long mSatelliteEmergencyModeDurationMillis;
     private static final int DEFAULT_SATELLITE_EMERGENCY_MODE_DURATION_SECONDS = 300;
+    private AlertDialog mNetworkSelectionModeAutoDialog = null;
 
     /** Key used to read/write satellite system notification done in shared preferences. */
     private static final String SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY =
@@ -573,6 +670,22 @@
     private static final String HOW_IT_WORKS_BUTTON = "how_it_works_button";
     private static final String ACTION_NOTIFICATION_CLICK = "action_notification_click";
     private static final String ACTION_NOTIFICATION_DISMISS = "action_notification_dismiss";
+    private AtomicBoolean mOverrideNtnEligibility;
+    private String mDefaultSmsPackageName = "";
+    private String mSatelliteGatewayServicePackageName = "";
+
+    private final Object mNtnSmsSupportedByMessagesAppLock = new Object();
+    @GuardedBy("mNtnSmsSupportedByMessagesAppLock")
+    private Boolean mNtnSmsSupportedByMessagesApp = null;
+
+    private final Object mSatelliteModemStateLock = new Object();
+    @GuardedBy("mSatelliteModemStateLock")
+    @SatelliteManager.SatelliteModemState
+    private int mSatelliteModemState = SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
+
+    // Data Plan types at entitlement for the plmn allowed
+    public static final int SATELLITE_DATA_PLAN_METERED = 0;
+    public static final int SATELLITE_DATA_PLAN_UNMETERED = 1;
     private BroadcastReceiver
             mDefaultSmsSubscriptionChangedBroadcastReceiver = new BroadcastReceiver() {
                 @Override
@@ -585,6 +698,119 @@
                 }
             };
 
+    private BroadcastReceiver mPackageStateChangedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mDefaultSmsPackageName = Telephony.Sms.getDefaultSmsPackage(mContext);
+            mSatelliteGatewayServicePackageName = getConfigSatelliteGatewayServicePackage();
+            String schemeSpecificPart = intent.getData().getSchemeSpecificPart();
+            plogd("packageStateChanged: " + intent.getData().toString()
+                    + " DefaultSmsPackageName:" + mDefaultSmsPackageName);
+
+            if (!schemeSpecificPart.equals(mSatelliteGatewayServicePackageName)
+                    && !schemeSpecificPart.equals(mDefaultSmsPackageName)) {
+                plogv("Neither SMS or SatelliteGateway package");
+                return;
+            }
+            int[] activeSubIds = mSubscriptionManagerService.getActiveSubIdList(true);
+            if (activeSubIds != null) {
+                for (int activeSubId : activeSubIds) {
+                    plogd("mPackageStateChangedReceiver: activeSubId= " + activeSubId);
+                    handleCarrierRoamingNtnAvailableServicesChanged(activeSubId);
+                }
+            } else {
+                ploge("mPackageStateChangedReceiver: activeSubIds is null");
+            }
+        }
+    };
+
+    // List of device states returned from DeviceStateManager to determine if running on a foldable
+    // device.
+    private List<DeviceState> mDeviceStates = new ArrayList();
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected final Object mSatelliteAccessConfigLock = new Object();
+    @GuardedBy("mSatelliteAccessConfigLock")
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected List<Integer> mCurrentLocationTagIds = new ArrayList();
+    @GuardedBy("mSatelliteAccessConfigLock")
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected boolean mSatelliteAccessAllowed = false;
+
+    public static final int RESULT_RECEIVER_COUNT_ANOMALY_THRESHOLD = 500;
+    protected final Object mResultReceiverTotalCountLock = new Object();
+    @GuardedBy("mResultReceiverTotalCountLock")
+    protected int mResultReceiverTotalCount;
+    @GuardedBy("mResultReceiverTotalCountLock")
+    protected HashMap<String, Integer> mResultReceiverCountPerMethodMap = new HashMap<>();
+
+    // Satellite anomaly uuid -- ResultReceiver count threshold exceeded
+    private final UUID mAnomalyUnexpectedResultReceiverCountUUID =
+            UUID.fromString("e268f22d-9bba-4d27-b76a-1c7f5b42e241");
+
+    private UUID generateAnomalyUnexpectedResultReceiverCountUUID(int error, int errorCode) {
+        long lerror = error;
+        long lerrorCode = errorCode;
+        return new UUID(mAnomalyUnexpectedResultReceiverCountUUID.getMostSignificantBits(),
+                mAnomalyUnexpectedResultReceiverCountUUID.getLeastSignificantBits()
+                        + ((lerrorCode << 32) + lerror));
+    }
+
+    /**
+     * Increments the ResultReceiver count and logs the caller information.
+     * If the count exceeds the threshold, it reports an anomaly via AnomalyReporter.
+     *
+     * @param caller The caller information that created the ResultReceiver
+     *               (e.g., class name and method name)
+     */
+    public void incrementResultReceiverCount(String caller) {
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            synchronized (mResultReceiverTotalCountLock) {
+                mResultReceiverTotalCount++;
+                logd("[incrementResultReceiverCount] : " + caller
+                        + " | ResultReceiver total count= " + mResultReceiverTotalCount);
+                mResultReceiverCountPerMethodMap.compute(caller,
+                        (k, v) -> v == null ? 1 : v + 1);
+
+                if (mResultReceiverTotalCount > RESULT_RECEIVER_COUNT_ANOMALY_THRESHOLD) {
+                    loge("[mResultReceiverTotalCount] is exceeds limits : "
+                            + mResultReceiverTotalCount);
+                    loge("[incrementResultReceiverCount] mResultReceiverCountPerMethodMap is "
+                            + mResultReceiverCountPerMethodMap);
+                    AnomalyReporter.reportAnomaly(
+                            generateAnomalyUnexpectedResultReceiverCountUUID(0, 0),
+                            "Satellite ResultReceiver total count= "
+                                    + mResultReceiverTotalCount + " exceeds limit.");
+                }
+            }
+        } else {
+            logd("[incrementResultReceiverCount]: carrierRoamingNbIotNtn is not enabled");
+        }
+    }
+
+    /**
+     * Decrements the ResultReceiver count and logs the caller information.
+     * Prevents the count from going below zero.
+     *
+     * @param caller The caller information that released the ResultReceiver
+     *               (e.g., class name and method name)
+     */
+    public void decrementResultReceiverCount(String caller) {
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            synchronized (mResultReceiverTotalCountLock) {
+                if (mResultReceiverTotalCount > 0) {
+                    mResultReceiverTotalCount--;
+                }
+                logd("[decrementResultReceiverCount] : " + caller
+                        + " | ResultReceiver total count=" + mResultReceiverTotalCount);
+                mResultReceiverCountPerMethodMap.computeIfPresent(caller,
+                        (k, v) -> v > 0 ? v - 1 : v);
+            }
+        } else {
+            logd("[decrementResultReceiverCount]: carrierRoamingNbIotNtn is not enabled");
+        }
+    }
+
     /**
      * @return The singleton instance of SatelliteController.
      */
@@ -669,6 +895,8 @@
         registerForPendingDatagramCount();
         registerForSatelliteModemStateChanged();
         registerForServiceStateChanged();
+        registerForSignalStrengthChanged();
+        registerForSatelliteCommunicationAllowedStateChanged();
         mContentResolver = mContext.getContentResolver();
         mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
 
@@ -689,6 +917,7 @@
         }
 
         mSatellitePlmnListFromOverlayConfig = readSatellitePlmnsFromOverlayConfig();
+        registerApplicationStateChanged();
         updateSupportedSatelliteServicesForActiveSubscriptions();
         mCarrierConfigChangeListener =
                 (slotIndex, subId, carrierId, specificCarrierId) ->
@@ -712,15 +941,25 @@
 
         mDSM.registerForSignalStrengthReportDecision(this, CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING,
                 null);
+
         loadSatelliteSharedPreferences();
+        if (mSharedPreferences != null) {
+            synchronized (mNtnSmsSupportedByMessagesAppLock) {
+                mNtnSmsSupportedByMessagesApp = mSharedPreferences.getBoolean(
+                        NTN_SMS_SUPPORTED_BY_MESSAGES_APP_KEY, false);
+            }
+        }
+
         mWaitTimeForSatelliteEnablingResponse = getWaitForSatelliteEnablingResponseTimeoutMillis();
         mDemoPointingAlignedDurationMillis = getDemoPointingAlignedDurationMillisFromResources();
         mDemoPointingNotAlignedDurationMillis =
                 getDemoPointingNotAlignedDurationMillisFromResources();
         mSatelliteEmergencyModeDurationMillis =
                 getSatelliteEmergencyModeDurationFromOverlayConfig(context);
+        mEvaluateEsosProfilesPrioritizationDurationMillis =
+                getEvaluateEsosProfilesPrioritizationDurationMillis();
         sendMessageDelayed(obtainMessage(CMD_EVALUATE_ESOS_PROFILES_PRIORITIZATION),
-                /* delayMillis= */ TimeUnit.MINUTES.toMillis(1));
+                mEvaluateEsosProfilesPrioritizationDurationMillis);
 
         SubscriptionManager subscriptionManager = mContext.getSystemService(
                 SubscriptionManager.class);
@@ -731,6 +970,9 @@
         }
         registerDefaultSmsSubscriptionChangedBroadcastReceiver();
         updateSatelliteProvisionedStatePerSubscriberId();
+        if (android.hardware.devicestate.feature.flags.Flags.deviceStatePropertyMigration()) {
+            mDeviceStates = getSupportedDeviceStates();
+        }
     }
 
     class SatelliteSubscriptionsChangedListener
@@ -902,15 +1144,13 @@
         public void onStateChanged(int state, int reason) {
             plogd("UwbAdapterStateCallback#onStateChanged() called, state = " + toString(state));
             plogd("Adapter state changed reason " + String.valueOf(reason));
-            synchronized (mRadioStateLock) {
-                if (state == UwbManager.AdapterStateCallback.STATE_DISABLED) {
-                    mUwbStateEnabled = false;
-                    evaluateToSendSatelliteEnabledSuccess();
-                } else {
-                    mUwbStateEnabled = true;
-                }
-                plogd("mUwbStateEnabled: " + mUwbStateEnabled);
+            if (state == UwbManager.AdapterStateCallback.STATE_DISABLED) {
+                setUwbEnabledState(false);
+                evaluateToSendSatelliteEnabledSuccess();
+            } else {
+                setUwbEnabledState(true);
             }
+            plogd("mUwbStateEnabled: " + getUwbEnabledState());
         }
     }
 
@@ -927,50 +1167,47 @@
                 case BluetoothAdapter.ACTION_STATE_CHANGED:
                     int btState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
                             BluetoothAdapter.ERROR);
-                    synchronized (mRadioStateLock) {
-                        boolean currentBTStateEnabled = mBTStateEnabled;
-                        if (btState == BluetoothAdapter.STATE_OFF) {
-                            mBTStateEnabled = false;
-                            evaluateToSendSatelliteEnabledSuccess();
-                        } else if (btState == BluetoothAdapter.STATE_ON) {
-                            mBTStateEnabled = true;
-                        }
-                        if (currentBTStateEnabled != mBTStateEnabled) {
-                            plogd("mBTStateEnabled=" + mBTStateEnabled);
-                        }
+                    boolean currentBTStateEnabled = getBTEnabledState();
+                    if (btState == BluetoothAdapter.STATE_OFF) {
+                        setBTEnabledState(false);
+                        evaluateToSendSatelliteEnabledSuccess();
+                    } else if (btState == BluetoothAdapter.STATE_ON) {
+                        setBTEnabledState(true);
+                    }
+
+                    if (currentBTStateEnabled != getBTEnabledState()) {
+                        plogd("mBTStateEnabled=" + getBTEnabledState());
                     }
                     break;
 
                 case NfcAdapter.ACTION_ADAPTER_STATE_CHANGED:
                     int nfcState = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE, -1);
-                    synchronized (mRadioStateLock) {
-                        boolean currentNfcStateEnabled = mNfcStateEnabled;
-                        if (nfcState == NfcAdapter.STATE_ON) {
-                            mNfcStateEnabled = true;
-                        } else if (nfcState == NfcAdapter.STATE_OFF) {
-                            mNfcStateEnabled = false;
-                            evaluateToSendSatelliteEnabledSuccess();
-                        }
-                        if (currentNfcStateEnabled != mNfcStateEnabled) {
-                            plogd("mNfcStateEnabled=" + mNfcStateEnabled);
-                        }
+                    boolean currentNfcStateEnabled = getNfcEnabledState();
+                    if (nfcState == NfcAdapter.STATE_ON) {
+                        setNfcEnabledState(true);
+                    } else if (nfcState == NfcAdapter.STATE_OFF) {
+                        setNfcEnabledState(false);
+                        evaluateToSendSatelliteEnabledSuccess();
+                    }
+
+                    if (currentNfcStateEnabled != getNfcEnabledState()) {
+                        plogd("mNfcStateEnabled=" + getNfcEnabledState());
                     }
                     break;
 
                 case WifiManager.WIFI_STATE_CHANGED_ACTION:
                     int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
                             WifiManager.WIFI_STATE_UNKNOWN);
-                    synchronized (mRadioStateLock) {
-                        boolean currentWifiStateEnabled = mWifiStateEnabled;
-                        if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
-                            mWifiStateEnabled = true;
-                        } else if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
-                            mWifiStateEnabled = false;
-                            evaluateToSendSatelliteEnabledSuccess();
-                        }
-                        if (currentWifiStateEnabled != mWifiStateEnabled) {
-                            plogd("mWifiStateEnabled=" + mWifiStateEnabled);
-                        }
+                    boolean currentWifiStateEnabled = getWifiEnabledState();
+                    if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
+                        setWifiEnabledState(true);
+                    } else if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
+                        setWifiEnabledState(false);
+                        evaluateToSendSatelliteEnabledSuccess();
+                    }
+
+                    if (currentWifiStateEnabled != getWifiEnabledState()) {
+                        plogd("mWifiStateEnabled=" + getWifiEnabledState());
                     }
                     break;
                 default:
@@ -1041,6 +1278,18 @@
         }
     }
 
+    private static final class UpdateSystemSelectionChannelsArgument {
+        @NonNull List<SystemSelectionSpecifier> mSystemSelectionSpecifiers;
+        @NonNull ResultReceiver mResult;
+
+        UpdateSystemSelectionChannelsArgument(
+                @NonNull List<SystemSelectionSpecifier> systemSelectionSpecifiers,
+                @NonNull ResultReceiver result) {
+            this.mSystemSelectionSpecifiers = systemSelectionSpecifiers;
+            this.mResult = result;
+        }
+    }
+
     /**
      * Arguments to send to SatelliteTransmissionUpdate registrants
      */
@@ -1193,11 +1442,12 @@
                         if (mNeedsSatellitePointing) {
                             mPointingAppController.removeListenerForPointingUI();
                         }
+
+                        if (!isWaitingForSatelliteModemOff()) {
+                            moveSatelliteToOffStateAndCleanUpResources(SATELLITE_RESULT_SUCCESS);
+                        }
+
                         synchronized (mSatelliteEnabledRequestLock) {
-                            if (!mWaitingForSatelliteModemOff) {
-                                moveSatelliteToOffStateAndCleanUpResources(
-                                        SATELLITE_RESULT_SUCCESS);
-                            }
                             mWaitingForDisableSatelliteModemResponse = false;
                         }
                     }
@@ -1219,14 +1469,15 @@
                     // If Satellite enable/disable request returned Error, no need to wait for radio
                     argument.callback.accept(error);
                 }
-
                 if (argument.enableSatellite) {
+                    mSessionMetricsStats.resetSessionStatsShadowCounters();
                     mSessionMetricsStats.setInitializationResult(error)
                             .setSatelliteTechnology(getSupportedNtnRadioTechnology())
                             .setInitializationProcessingTime(
                                     System.currentTimeMillis() - mSessionProcessingTimeStamp)
                             .setIsDemoMode(mIsDemoModeEnabled)
-                            .setCarrierId(getSatelliteCarrierId());
+                            .setCarrierId(getSatelliteCarrierId())
+                            .setIsEmergency(argument.isEmergency);
                     mSessionProcessingTimeStamp = 0;
 
                     if (error == SATELLITE_RESULT_SUCCESS) {
@@ -1290,8 +1541,20 @@
                     }
                 }
                 onCompleted = obtainMessage(EVENT_UPDATE_SATELLITE_ENABLE_ATTRIBUTES_DONE, request);
+                SatelliteModemEnableRequestAttributes enableRequestAttributes =
+                    createModemEnableRequest(argument);
+                if (enableRequestAttributes == null) {
+                    plogw("UpdateEnableAttributes: enableRequestAttributes is null");
+                    sendErrorAndReportSessionMetrics(
+                        SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                        argument.callback);
+                    synchronized (mSatelliteEnabledRequestLock) {
+                        mSatelliteEnableAttributesUpdateRequest = null;
+                    }
+                    break;
+                }
                 mSatelliteModemInterface.requestSatelliteEnabled(
-                        createModemEnableRequest(argument), onCompleted);
+                        enableRequestAttributes, onCompleted);
                 startWaitForUpdateSatelliteEnableAttributesResponseTimer(argument);
                 break;
             }
@@ -1365,6 +1628,7 @@
                     updateSatelliteSupportedState(false);
                 }
                 ((ResultReceiver) request.argument).send(error, bundle);
+                decrementResultReceiverCount("SC:requestIsSatelliteEnabled");
                 break;
             }
 
@@ -1417,12 +1681,14 @@
                         synchronized (mNeedsSatellitePointingLock) {
                             mNeedsSatellitePointing = capabilities.isPointingRequired();
                         }
-                        if (DBG) plogd("getSatelliteCapabilities: " + capabilities);
-                        bundle.putParcelable(SatelliteManager.KEY_SATELLITE_CAPABILITIES,
-                                capabilities);
+
                         synchronized (mSatelliteCapabilitiesLock) {
                             mSatelliteCapabilities = capabilities;
                         }
+                        overrideSatelliteCapabilitiesIfApplicable();
+                        if (DBG) plogd("getSatelliteCapabilities: " + getSatelliteCapabilities());
+                        bundle.putParcelable(SatelliteManager.KEY_SATELLITE_CAPABILITIES,
+                                getSatelliteCapabilities());
                     }
                 }
                 ((ResultReceiver) request.argument).send(error, bundle);
@@ -1458,6 +1724,7 @@
                     }
                 }
                 ((ResultReceiver) request.argument).send(error, bundle);
+                decrementResultReceiverCount("SC:requestTimeForNextSatelliteVisibility");
                 break;
             }
 
@@ -1481,19 +1748,21 @@
 
                 if (mCi.getRadioState() != TelephonyManager.RADIO_POWER_UNAVAILABLE) {
                     if (mSatelliteModemInterface.isSatelliteServiceConnected()) {
-                        synchronized (mIsSatelliteSupportedLock) {
-                            if (mIsSatelliteSupported == null || !mIsSatelliteSupported) {
-                                ResultReceiver receiver = new ResultReceiver(this) {
-                                    @Override
-                                    protected void onReceiveResult(
-                                            int resultCode, Bundle resultData) {
-                                        plogd("onRadioStateChanged.requestIsSatelliteSupported: "
-                                                + "resultCode=" + resultCode
-                                                + ", resultData=" + resultData);
-                                    }
-                                };
-                                sendRequestAsync(CMD_IS_SATELLITE_SUPPORTED, receiver, null);
-                            }
+                        Boolean isSatelliteSupported = getIsSatelliteSupported();
+                        if (isSatelliteSupported == null || !isSatelliteSupported) {
+                            final String caller = "SC:CMD_IS_SATELLITE_SUPPORTED";
+                            ResultReceiver receiver = new ResultReceiver(this) {
+                                @Override
+                                protected void onReceiveResult(
+                                        int resultCode, Bundle resultData) {
+                                    decrementResultReceiverCount(caller);
+                                    plogd("onRadioStateChanged.requestIsSatelliteSupported: "
+                                            + "resultCode=" + resultCode
+                                            + ", resultData=" + resultData);
+                                }
+                            };
+                            sendRequestAsync(CMD_IS_SATELLITE_SUPPORTED, receiver, null);
+                            incrementResultReceiverCount(caller);
                         }
                     }
                 }
@@ -1531,6 +1800,7 @@
                     ploge("EVENT_SATELLITE_MODEM_STATE_CHANGED: result is null");
                 } else {
                     handleEventSatelliteModemStateChanged((int) ar.result);
+                    updateLastNotifiedCarrierRoamingNtnSignalStrengthAndNotify(getSatellitePhone());
                 }
                 break;
 
@@ -1610,6 +1880,7 @@
                     }
                     result.send(errorCode, null);
                 }
+                decrementResultReceiverCount("SC:requestNtnSignalStrength");
                 break;
             }
 
@@ -1619,6 +1890,7 @@
                     ploge("EVENT_NTN_SIGNAL_STRENGTH_CHANGED: result is null");
                 } else {
                     handleEventNtnSignalStrengthChanged((NtnSignalStrength) ar.result);
+                    updateLastNotifiedCarrierRoamingNtnSignalStrengthAndNotify(getSatellitePhone());
                 }
                 break;
             }
@@ -1695,15 +1967,10 @@
             }
 
             case EVENT_NOTIFY_NTN_ELIGIBILITY_HYSTERESIS_TIMED_OUT: {
-                synchronized (mSatellitePhoneLock) {
-                    mNtnEligibilityHysteresisTimedOut = true;
-                    boolean eligible = isCarrierRoamingNtnEligible(mSatellitePhone);
-                    plogd("EVENT_NOTIFY_NTN_ELIGIBILITY_HYSTERESIS_TIMED_OUT:"
-                            + " isCarrierRoamingNtnEligible=" + eligible);
-                    if (eligible) {
-                        requestIsSatelliteAllowedForCurrentLocation();
-                    }
-                }
+                boolean eligible = isCarrierRoamingNtnEligible(mSatellitePhone);
+                plogd("EVENT_NOTIFY_NTN_ELIGIBILITY_HYSTERESIS_TIMED_OUT:"
+                        + " isCarrierRoamingNtnEligible=" + eligible);
+                updateLastNotifiedNtnEligibilityAndNotify(eligible);
                 break;
             }
 
@@ -1717,35 +1984,38 @@
                 RequestProvisionSatelliteArgument argument =
                         (RequestProvisionSatelliteArgument) request.argument;
                 onCompleted = obtainMessage(EVENT_UPDATE_PROVISION_SATELLITE_TOKEN_DONE, request);
-                // only pass to index 0.
-                // TODO: Select the subscription with highest priority and set it to mSatelliteSubId
-                int subId = -1;
-                synchronized (mSatelliteTokenProvisionedLock) {
-                    subId = mSubscriberIdPerSub.getOrDefault(
-                            argument.mSatelliteSubscriberInfoList.get(0).getSubscriberId(), -1);
-                }
-                setSatellitePhone(subId);
-                String iccId = mSubscriptionManagerService.getSubscriptionInfo(subId).getIccId();
-                argument.setIccId(iccId);
-                boolean sendResponse = false;
-                synchronized (mSatelliteTokenProvisionedLock) {
-                    if (!iccId.equals(mLastConfiguredIccId)) {
-                        logd("updateSatelliteSubscription subId=" + subId + ", iccId=" + iccId
-                                + " to modem");
-                        mSatelliteModemInterface.updateSatelliteSubscription(iccId, onCompleted);
-                    } else {
-                        sendResponse = true;
+                boolean provisionChanged = updateSatelliteSubscriptionProvisionState(
+                        argument.mSatelliteSubscriberInfoList, argument.mProvisioned);
+                selectBindingSatelliteSubscription(false);
+                int subId = getSelectedSatelliteSubId();
+                SubscriptionInfo subscriptionInfo =
+                    mSubscriptionManagerService.getSubscriptionInfo(subId);
+                if (subscriptionInfo == null) {
+                    logw("updateSatelliteToken subId=" + subId + " is not found");
+                } else {
+                    String iccId = subscriptionInfo.getIccId();
+                    argument.setIccId(iccId);
+                    synchronized (mSatelliteTokenProvisionedLock) {
+                        if (!iccId.equals(mLastConfiguredIccId)) {
+                            logd("updateSatelliteSubscription subId=" + subId
+                                    + ", iccId=" + iccId + " to modem");
+                            mSatelliteModemInterface.updateSatelliteSubscription(
+                                iccId, onCompleted);
+                        }
                     }
                 }
-                handleEventSatelliteSubscriptionProvisionStateChanged(
-                        argument.mSatelliteSubscriberInfoList, true);
-                if (sendResponse) {
-                    // The response is sent immediately because the ICCID has already been
-                    // delivered to the modem.
-                    Bundle bundle = new Bundle();
-                    bundle.putBoolean(SatelliteManager.KEY_PROVISION_SATELLITE_TOKENS, true);
-                    argument.mResult.send(SATELLITE_RESULT_SUCCESS, bundle);
+                if (provisionChanged) {
+                    handleEventSatelliteSubscriptionProvisionStateChanged();
                 }
+
+                // The response is sent immediately because the ICCID has already been
+                // delivered to the modem.
+                Bundle bundle = new Bundle();
+                bundle.putBoolean(
+                        argument.mProvisioned ? SatelliteManager.KEY_PROVISION_SATELLITE_TOKENS
+                                : SatelliteManager.KEY_DEPROVISION_SATELLITE_TOKENS, true);
+                argument.mResult.send(SATELLITE_RESULT_SUCCESS, bundle);
+                decrementResultReceiverCount("SC:provisionSatellite");
                 break;
             }
 
@@ -1761,11 +2031,17 @@
                         mLastConfiguredIccId = argument.getIccId();
                     }
                 }
+                mProvisionMetricsStats.setResultCode(error)
+                        .setIsProvisionRequest(argument.mProvisioned)
+                        .setCarrierId(getSatelliteCarrierId())
+                        .setIsNtnOnlyCarrier(isNtnOnlyCarrier())
+                        .reportProvisionMetrics();
+                if (argument.mProvisioned) {
+                    mControllerMetricsStats.reportProvisionCount(error);
+                } else {
+                    mControllerMetricsStats.reportDeprovisionCount(error);
+                }
                 logd("updateSatelliteSubscription result=" + error);
-                Bundle bundle = new Bundle();
-                bundle.putBoolean(SatelliteManager.KEY_PROVISION_SATELLITE_TOKENS,
-                        error == SATELLITE_RESULT_SUCCESS);
-                argument.mResult.send(error, bundle);
                 break;
             }
 
@@ -1775,12 +2051,8 @@
                     mIsWifiConnected = (boolean) ar.result;
                     plogd("EVENT_WIFI_CONNECTIVITY_STATE_CHANGED: mIsWifiConnected="
                             + mIsWifiConnected);
-                    handleStateChangedForCarrierRoamingNtnEligibility();
                 }
-                break;
-            }
-            case EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT: {
-                handleSatelliteAccessRestrictionCheckingResult((boolean) msg.obj);
+                evaluateCarrierRoamingNtnEligibilityChange();
                 break;
             }
 
@@ -1815,6 +2087,76 @@
                 }
                 break;
 
+            case EVENT_TERRESTRIAL_NETWORK_AVAILABLE_CHANGED:
+                ar = (AsyncResult) msg.obj;
+                if (ar.result == null) {
+                    loge("EVENT_TERRESTRIAL_NETWORK_AVAILABLE_CHANGED: result is null");
+                } else {
+                    handleEventTerrestrialNetworkAvailableChanged((boolean) ar.result);
+                }
+                break;
+
+            case EVENT_SET_NETWORK_SELECTION_AUTO_DONE: {
+                logd("EVENT_SET_NETWORK_SELECTION_AUTO_DONE");
+                RequestSatelliteEnabledArgument argument =
+                        (RequestSatelliteEnabledArgument) msg.obj;
+                sendRequestAsync(CMD_SET_SATELLITE_ENABLED, argument, null);
+                break;
+            }
+
+            case EVENT_SIGNAL_STRENGTH_CHANGED: {
+                ar = (AsyncResult) msg.obj;
+                int phoneId = (int) ar.userObj;
+                updateLastNotifiedCarrierRoamingNtnSignalStrengthAndNotify(
+                        PhoneFactory.getPhone(phoneId));
+                break;
+            }
+
+            case CMD_UPDATE_SYSTEM_SELECTION_CHANNELS: {
+                plogd("CMD_UPDATE_SYSTEM_SELECTION_CHANNELS");
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                onCompleted = obtainMessage(EVENT_UPDATE_SYSTEM_SELECTION_CHANNELS_DONE, request);
+                mSatelliteModemInterface.updateSystemSelectionChannels(
+                        ((UpdateSystemSelectionChannelsArgument) (request.argument))
+                                .mSystemSelectionSpecifiers,
+                        onCompleted);
+                break;
+            }
+
+            case EVENT_UPDATE_SYSTEM_SELECTION_CHANNELS_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                int error =  SatelliteServiceUtils.getSatelliteError(
+                        ar, "updateSystemSelectionChannel");
+                plogd("EVENT_UPDATE_SYSTEM_SELECTION_CHANNELS_DONE = " + error);
+                ((UpdateSystemSelectionChannelsArgument) (request.argument)).mResult.send(error,
+                        null);
+                break;
+            }
+
+            case EVENT_SELECTED_NB_IOT_SATELLITE_SUBSCRIPTION_CHANGED: {
+                ar = (AsyncResult) msg.obj;
+                if (ar.result == null) {
+                    loge("EVENT_SELECTED_NB_IOT_SATELLITE_SUBSCRIPTION_CHANGED: result is null");
+                } else {
+                    handleEventSelectedNbIotSatelliteSubscriptionChanged((int) ar.result);
+                }
+                break;
+            }
+
+            case CMD_EVALUATE_CARRIER_ROAMING_NTN_ELIGIBILITY_CHANGE: {
+                plogd("CMD_EVALUATE_CARRIER_ROAMING_NTN_ELIGIBILITY_CHANGE");
+                evaluateCarrierRoamingNtnEligibilityChange();
+                boolean eligible = isCarrierRoamingNtnEligible(getSatellitePhone());
+                plogd("CMD_EVALUATE_CARRIER_ROAMING_NTN_ELIGIBILITY_CHANGE: eligible=" + eligible);
+                int selectedSatelliteSubId = getSelectedSatelliteSubId();
+                Phone phone = SatelliteServiceUtils.getPhone(selectedSatelliteSubId);
+                if (eligible) {
+                    phone.notifyCarrierRoamingNtnEligibleStateChanged(eligible);
+                }
+                break;
+            }
+
             default:
                 Log.w(TAG, "SatelliteControllerHandler: unexpected message code: " +
                         msg.what);
@@ -1828,11 +2170,13 @@
         public ResultReceiver mResult;
         public long mRequestId;
         public String mIccId;
+        public boolean mProvisioned;
 
         RequestProvisionSatelliteArgument(List<SatelliteSubscriberInfo> satelliteSubscriberInfoList,
-                ResultReceiver result) {
+                ResultReceiver result, boolean provisioned) {
             this.mSatelliteSubscriberInfoList = satelliteSubscriberInfoList;
             this.mResult = result;
+            this.mProvisioned = provisioned;
             this.mRequestId = sNextSatelliteEnableRequestId.getAndUpdate(
                     n -> ((n + 1) % Long.MAX_VALUE));
         }
@@ -1927,16 +2271,29 @@
          *      SATELLITE_RESULT_ERROR error
          *      4. ongoing request = enable, current request = disable: send request to modem
          */
+        Boolean isSatelliteEnabled = getIsSatelliteEnabled();
         synchronized (mSatelliteEnabledRequestLock) {
-            if (!isSatelliteEnabledRequestInProgress()) {
-                synchronized (mIsSatelliteEnabledLock) {
-                    if (mIsSatelliteEnabled != null && mIsSatelliteEnabled == enableSatellite) {
-                        evaluateToUpdateSatelliteEnabledAttributes(result,
-                                SatelliteManager.SATELLITE_RESULT_SUCCESS, request,
-                                mIsDemoModeEnabled, mIsEmergency);
-                        return;
-                    }
+            if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+                if (mSatelliteEnabledRequest != null && mNetworkSelectionModeAutoDialog != null
+                        && mNetworkSelectionModeAutoDialog.isShowing()
+                        && request.isEmergency && request.enableSatellite) {
+                    sendErrorAndReportSessionMetrics(
+                            SatelliteManager.SATELLITE_RESULT_ILLEGAL_STATE,
+                            FunctionalUtils.ignoreRemoteException(
+                                    mSatelliteEnabledRequest.callback::accept));
+                    mSatelliteEnabledRequest = null;
+                    mNetworkSelectionModeAutoDialog.dismiss();
+                    mNetworkSelectionModeAutoDialog = null;
                 }
+            }
+            if (!isSatelliteEnabledRequestInProgress()) {
+                if (isSatelliteEnabled != null && isSatelliteEnabled == enableSatellite) {
+                    evaluateToUpdateSatelliteEnabledAttributes(result,
+                            SatelliteManager.SATELLITE_RESULT_SUCCESS, request,
+                            mIsDemoModeEnabled, mIsEmergency);
+                    return;
+                }
+
                 if (enableSatellite) {
                     mSatelliteEnabledRequest = request;
                 } else {
@@ -1990,7 +2347,87 @@
                 }
             }
         }
-        sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, null);
+
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            Phone satellitePhone = getSatellitePhone();
+            if (enableSatellite && satellitePhone != null
+                    && satellitePhone.getServiceStateTracker() != null
+                    && satellitePhone.getServiceStateTracker().getServiceState()
+                    .getIsManualSelection()) {
+                checkNetworkSelectionModeAuto(request);
+            } else {
+                sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, null);
+            }
+        } else {
+            sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, null);
+        }
+    }
+
+    private void checkNetworkSelectionModeAuto(RequestSatelliteEnabledArgument argument) {
+        plogd("checkNetworkSelectionModeAuto");
+        if (argument.isEmergency) {
+            // ESOS
+            getSatellitePhone().setNetworkSelectionModeAutomatic(null);
+            sendMessageDelayed(obtainMessage(EVENT_SET_NETWORK_SELECTION_AUTO_DONE, argument),
+                    DELAY_WAITING_SET_NETWORK_SELECTION_AUTO_MILLIS);
+        } else {
+            // P2P
+            if (mNetworkSelectionModeAutoDialog != null
+                    && mNetworkSelectionModeAutoDialog.isShowing()) {
+                logd("requestSatelliteEnabled: already auto network selection mode popup showing");
+                sendErrorAndReportSessionMetrics(
+                        SatelliteManager.SATELLITE_RESULT_REQUEST_IN_PROGRESS,
+                        FunctionalUtils.ignoreRemoteException(argument.callback::accept));
+                return;
+            }
+            logd("requestSatelliteEnabled: auto network selection mode popup");
+            Configuration configuration = Resources.getSystem().getConfiguration();
+            boolean nightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                    == Configuration.UI_MODE_NIGHT_YES;
+
+            AlertDialog.Builder builder = new AlertDialog.Builder(mContext, nightMode
+                    ? AlertDialog.THEME_DEVICE_DEFAULT_DARK
+                    : AlertDialog.THEME_DEVICE_DEFAULT_LIGHT);
+
+            String title = mContext.getResources().getString(
+                    R.string.satellite_manual_selection_state_popup_title);
+            String message = mContext.getResources().getString(
+                    R.string.satellite_manual_selection_state_popup_message);
+            String ok = mContext.getResources().getString(
+                    R.string.satellite_manual_selection_state_popup_ok);
+            String cancel = mContext.getResources().getString(
+                    R.string.satellite_manual_selection_state_popup_cancel);
+
+            builder.setTitle(title).setMessage(message)
+                    .setPositiveButton(ok, (dialog, which) -> {
+                        logd("checkNetworkSelectionModeAuto: setPositiveButton");
+                        getSatellitePhone().setNetworkSelectionModeAutomatic(null);
+                        sendMessageDelayed(obtainMessage(EVENT_SET_NETWORK_SELECTION_AUTO_DONE,
+                                argument), DELAY_WAITING_SET_NETWORK_SELECTION_AUTO_MILLIS);
+                    })
+                    .setNegativeButton(cancel, (dialog, which) -> {
+                        logd("checkNetworkSelectionModeAuto: setNegativeButton");
+                        synchronized (mSatelliteEnabledRequestLock) {
+                            mSatelliteEnabledRequest = null;
+                        }
+                        sendErrorAndReportSessionMetrics(
+                                SatelliteManager.SATELLITE_RESULT_ILLEGAL_STATE,
+                                FunctionalUtils.ignoreRemoteException(argument.callback::accept));
+                    })
+                    .setOnCancelListener(dialog -> {
+                        logd("checkNetworkSelectionModeAuto: setOnCancelListener");
+                        synchronized (mSatelliteEnabledRequestLock) {
+                            mSatelliteEnabledRequest = null;
+                        }
+                        sendErrorAndReportSessionMetrics(
+                                SatelliteManager.SATELLITE_RESULT_ILLEGAL_STATE,
+                                FunctionalUtils.ignoreRemoteException(argument.callback::accept));
+                    });
+            mNetworkSelectionModeAutoDialog = builder.create();
+            mNetworkSelectionModeAutoDialog.getWindow()
+                    .setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+            mNetworkSelectionModeAutoDialog.show();
+        }
     }
 
     /**
@@ -2075,17 +2512,17 @@
             return;
         }
 
-        synchronized (mIsSatelliteEnabledLock) {
-            if (mIsSatelliteEnabled != null) {
-                /* We have already successfully queried the satellite modem. */
-                Bundle bundle = new Bundle();
-                bundle.putBoolean(SatelliteManager.KEY_SATELLITE_ENABLED, mIsSatelliteEnabled);
-                result.send(SATELLITE_RESULT_SUCCESS, bundle);
-                return;
-            }
+        Boolean isSatelliteEnabled = getIsSatelliteEnabled();
+        if (isSatelliteEnabled != null) {
+            /* We have already successfully queried the satellite modem. */
+            Bundle bundle = new Bundle();
+            bundle.putBoolean(SatelliteManager.KEY_SATELLITE_ENABLED, isSatelliteEnabled);
+            result.send(SATELLITE_RESULT_SUCCESS, bundle);
+            return;
         }
 
         sendRequestAsync(CMD_IS_SATELLITE_ENABLED, result, null);
+        incrementResultReceiverCount("SC:requestIsSatelliteEnabled");
     }
 
     /**
@@ -2094,13 +2531,15 @@
      *
      * @return {@code true} if the satellite modem is enabled and {@code false} otherwise.
      */
-    public boolean isSatelliteEnabled() {
+    private boolean isSatelliteEnabled() {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             plogd("isSatelliteEnabled: oemEnabledSatelliteFlag is disabled");
             return false;
         }
-        if (mIsSatelliteEnabled == null) return false;
-        return mIsSatelliteEnabled;
+        synchronized (mIsSatelliteEnabledLock) {
+            if (mIsSatelliteEnabled == null) return false;
+            return mIsSatelliteEnabled;
+        }
     }
 
     /**
@@ -2108,16 +2547,31 @@
      *
      * @return {@code true} if the satellite modem is being enabled and {@code false} otherwise.
      */
-    public boolean isSatelliteBeingEnabled() {
+    private boolean isSatelliteBeingEnabled() {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             plogd("isSatelliteBeingEnabled: oemEnabledSatelliteFlag is disabled");
             return false;
         }
 
-        if (mSatelliteSessionController != null) {
-            return mSatelliteSessionController.isInEnablingState();
+        if (mSatelliteSessionController != null
+                && mSatelliteSessionController.isInEnablingState()) {
+            return true;
         }
-        return false;
+
+        synchronized (mSatelliteEnabledRequestLock) {
+            return (mSatelliteEnabledRequest != null);
+        }
+    }
+
+    /**
+     * Get whether the satellite modem is enabled or being enabled.
+     * This will return the cached value instead of querying the satellite modem.
+     *
+     * @return {@code true} if the satellite modem is enabled or being enabled, {@code false}
+     * otherwise.
+     */
+    public boolean isSatelliteEnabledOrBeingEnabled() {
+        return isSatelliteEnabled() || isSatelliteBeingEnabled();
     }
 
     /**
@@ -2126,10 +2580,14 @@
      * @return {@code true} if the satellite modem is being disabled and {@code false} otherwise.
      */
     public boolean isSatelliteBeingDisabled() {
-        if (mSatelliteSessionController != null) {
-            return mSatelliteSessionController.isInDisablingState();
+        if (mSatelliteSessionController != null
+                && mSatelliteSessionController.isInDisablingState()) {
+            return true;
         }
-        return false;
+
+        synchronized (mSatelliteEnabledRequestLock) {
+            return (mSatelliteDisabledRequest != null);
+        }
     }
 
     /**
@@ -2196,15 +2654,16 @@
             result.send(SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED, null);
             return;
         }
-        synchronized (mIsSatelliteSupportedLock) {
-            if (mIsSatelliteSupported != null) {
-                /* We have already successfully queried the satellite modem. */
-                Bundle bundle = new Bundle();
-                bundle.putBoolean(SatelliteManager.KEY_SATELLITE_SUPPORTED, mIsSatelliteSupported);
-                bundle.putInt(SATELLITE_SUBSCRIPTION_ID, getHighestPrioritySubscrption());
-                result.send(SATELLITE_RESULT_SUCCESS, bundle);
-                return;
-            }
+
+        int subId = getSelectedSatelliteSubId();
+        Boolean isSatelliteSupported = getIsSatelliteSupported();
+        if (isSatelliteSupported != null) {
+            /* We have already successfully queried the satellite modem. */
+            Bundle bundle = new Bundle();
+            bundle.putBoolean(SatelliteManager.KEY_SATELLITE_SUPPORTED, isSatelliteSupported);
+            bundle.putInt(SATELLITE_SUBSCRIPTION_ID, subId);
+            result.send(SATELLITE_RESULT_SUCCESS, bundle);
+            return;
         }
 
         sendRequestAsync(CMD_IS_SATELLITE_SUPPORTED, result, null);
@@ -2223,14 +2682,13 @@
             return;
         }
 
-        synchronized (mSatelliteCapabilitiesLock) {
-            if (mSatelliteCapabilities != null) {
-                Bundle bundle = new Bundle();
-                bundle.putParcelable(SatelliteManager.KEY_SATELLITE_CAPABILITIES,
-                        mSatelliteCapabilities);
-                result.send(SATELLITE_RESULT_SUCCESS, bundle);
-                return;
-            }
+        if (getSatelliteCapabilities() != null) {
+            Bundle bundle = new Bundle();
+            overrideSatelliteCapabilitiesIfApplicable();
+            bundle.putParcelable(SatelliteManager.KEY_SATELLITE_CAPABILITIES,
+                    getSatelliteCapabilities());
+            result.send(SATELLITE_RESULT_SUCCESS, bundle);
+            return;
         }
 
         sendRequestAsync(CMD_GET_SATELLITE_CAPABILITIES, result, null);
@@ -2254,7 +2712,7 @@
             return;
         }
 
-        final int validSubId = getHighestPrioritySubscrption();
+        final int validSubId = getSelectedSatelliteSubId();
         mPointingAppController.registerForSatelliteTransmissionUpdates(validSubId, callback);
         sendRequestAsync(CMD_START_SATELLITE_TRANSMISSION_UPDATES,
                 new SatelliteTransmissionUpdateArgument(result, callback, validSubId), null);
@@ -2272,7 +2730,7 @@
             @NonNull ISatelliteTransmissionUpdateCallback callback) {
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(errorCallback::accept);
         mPointingAppController.unregisterForSatelliteTransmissionUpdates(
-                getHighestPrioritySubscrption(), result, callback);
+                getSelectedSatelliteSubId(), result, callback);
 
         // Even if handler is null - which means there are no listeners, the modem command to stop
         // satellite transmission updates might have failed. The callers might want to retry
@@ -2296,36 +2754,60 @@
             @NonNull String token, @NonNull byte[] provisionData,
             @NonNull IIntegerConsumer callback) {
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
-        int error = evaluateOemSatelliteRequestAllowed(false);
-        if (error != SATELLITE_RESULT_SUCCESS) {
-            result.accept(error);
-            return null;
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            List<SatelliteSubscriberInfo> subscriberInfoList =
+                    getNtnOnlySatelliteSubscriberInfoList(result);
+            if (subscriberInfoList == null) {
+                return null;
+            }
+            ResultReceiver internalReceiver = new ResultReceiver(this) {
+                @Override
+                protected void onReceiveResult(int resultCode, Bundle resultData) {
+                    plogd("provisionSatelliteService: resultCode=" + resultCode
+                              + ", resultData=" + resultData);
+                    result.accept(resultCode);
+                }
+            };
+            provisionSatellite(subscriberInfoList, internalReceiver);
+
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+            CancellationSignal.fromTransport(cancelTransport).setOnCancelListener(() -> {
+                deprovisionSatellite(subscriberInfoList, internalReceiver);
+                mProvisionMetricsStats.setIsCanceled(true);
+            });
+            return cancelTransport;
+        } else {
+            int error = evaluateOemSatelliteRequestAllowed(false);
+            if (error != SATELLITE_RESULT_SUCCESS) {
+                result.accept(error);
+                return null;
+            }
+
+            final int validSubId = getSelectedSatelliteSubId();
+            if (mSatelliteProvisionCallbacks.containsKey(validSubId)) {
+                result.accept(SatelliteManager.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS);
+                return null;
+            }
+
+            Boolean satelliteProvisioned = isDeviceProvisioned();
+            if (satelliteProvisioned != null && satelliteProvisioned) {
+                result.accept(SATELLITE_RESULT_SUCCESS);
+                return null;
+            }
+
+            sendRequestAsync(CMD_PROVISION_SATELLITE_SERVICE,
+                    new ProvisionSatelliteServiceArgument(token, provisionData, result, validSubId),
+                    null);
+
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+            CancellationSignal.fromTransport(cancelTransport).setOnCancelListener(() -> {
+                sendRequestAsync(CMD_DEPROVISION_SATELLITE_SERVICE,
+                        new ProvisionSatelliteServiceArgument(token, provisionData, null,
+                                validSubId), null);
+                mProvisionMetricsStats.setIsCanceled(true);
+            });
+            return cancelTransport;
         }
-
-        final int validSubId = getHighestPrioritySubscrption();
-        if (mSatelliteProvisionCallbacks.containsKey(validSubId)) {
-            result.accept(SatelliteManager.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS);
-            return null;
-        }
-
-        Boolean satelliteProvisioned = isSatelliteViaOemProvisioned();
-        if (satelliteProvisioned != null && satelliteProvisioned) {
-            result.accept(SATELLITE_RESULT_SUCCESS);
-            return null;
-        }
-
-        sendRequestAsync(CMD_PROVISION_SATELLITE_SERVICE,
-                new ProvisionSatelliteServiceArgument(token, provisionData, result, validSubId),
-                null);
-
-        ICancellationSignal cancelTransport = CancellationSignal.createTransport();
-        CancellationSignal.fromTransport(cancelTransport).setOnCancelListener(() -> {
-            sendRequestAsync(CMD_DEPROVISION_SATELLITE_SERVICE,
-                    new ProvisionSatelliteServiceArgument(token, provisionData, null,
-                            validSubId), null);
-            mProvisionMetricsStats.setIsCanceled(true);
-        });
-        return cancelTransport;
     }
 
     /**
@@ -2341,21 +2823,38 @@
     public void deprovisionSatelliteService(
             @NonNull String token, @NonNull IIntegerConsumer callback) {
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
-        int error = evaluateOemSatelliteRequestAllowed(false);
-        if (error != SATELLITE_RESULT_SUCCESS) {
-            result.accept(error);
-            return;
-        }
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            List<SatelliteSubscriberInfo> subscriberInfoList =
+                    getNtnOnlySatelliteSubscriberInfoList(result);
+            if (subscriberInfoList == null) {
+                return;
+            }
+            ResultReceiver internalReceiver = new ResultReceiver(this) {
+                @Override
+                protected void onReceiveResult(int resultCode, Bundle resultData) {
+                    plogd("deprovisionSatelliteService: resultCode=" + resultCode
+                              + ", resultData=" + resultData);
+                    result.accept(resultCode);
+                }
+            };
+            deprovisionSatellite(subscriberInfoList, internalReceiver);
+        } else {
+            int error = evaluateOemSatelliteRequestAllowed(false);
+            if (error != SATELLITE_RESULT_SUCCESS) {
+                result.accept(error);
+                return;
+            }
 
-        if (Boolean.FALSE.equals(isSatelliteViaOemProvisioned())) {
-            result.accept(SATELLITE_RESULT_SUCCESS);
-            return;
-        }
+            if (Boolean.FALSE.equals(isDeviceProvisioned())) {
+                result.accept(SATELLITE_RESULT_SUCCESS);
+                return;
+            }
 
-        sendRequestAsync(CMD_DEPROVISION_SATELLITE_SERVICE,
+            sendRequestAsync(CMD_DEPROVISION_SATELLITE_SERVICE,
                 new ProvisionSatelliteServiceArgument(token, null,
-                        result, getHighestPrioritySubscrption()),
+                        result, getSelectedSatelliteSubId()),
                 null);
+        }
     }
 
     /**
@@ -2367,20 +2866,15 @@
      */
     @SatelliteManager.SatelliteResult public int registerForSatelliteProvisionStateChanged(
             @NonNull ISatelliteProvisionStateCallback callback) {
-        int error = evaluateOemSatelliteRequestAllowed(false);
-        if (error != SATELLITE_RESULT_SUCCESS) {
-            return error;
-        }
-
         mSatelliteProvisionStateChangedListeners.put(callback.asBinder(), callback);
 
-        boolean isProvisioned = Boolean.TRUE.equals(isSatelliteViaOemProvisioned());
+        boolean isProvisioned = Boolean.TRUE.equals(isDeviceProvisioned());
         try {
             callback.onSatelliteProvisionStateChanged(isProvisioned);
         } catch (RemoteException ex) {
             loge("registerForSatelliteProvisionStateChanged: " + ex);
         }
-        synchronized (mSatelliteViaOemProvisionLock) {
+        synchronized (mDeviceProvisionLock) {
             plogd("registerForSatelliteProvisionStateChanged: report current provisioned "
                     + "state, state=" + isProvisioned);
         }
@@ -2419,17 +2913,18 @@
             return;
         }
 
-        synchronized (mSatelliteViaOemProvisionLock) {
-            if (mIsSatelliteViaOemProvisioned != null) {
+        synchronized (mDeviceProvisionLock) {
+            if (mIsDeviceProvisioned != null) {
                 Bundle bundle = new Bundle();
                 bundle.putBoolean(SatelliteManager.KEY_SATELLITE_PROVISIONED,
-                        mIsSatelliteViaOemProvisioned);
+                        mIsDeviceProvisioned);
                 result.send(SATELLITE_RESULT_SUCCESS, bundle);
                 return;
             }
         }
 
         sendRequestAsync(CMD_IS_SATELLITE_PROVISIONED, result, null);
+        incrementResultReceiverCount("SC:requestIsSatelliteProvisioned");
     }
 
     /**
@@ -2446,8 +2941,9 @@
             return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
         }
         if (mFeatureFlags.carrierRoamingNbIotNtn()) {
-            plogd("registerForSatelliteModemStateChanged: add RegistrationFailure Listeners");
+            plogd("registerForSatelliteModemStateChanged: add Listeners for ModemState");
             mSatelliteRegistrationFailureListeners.put(callback.asBinder(), callback);
+            mTerrestrialNetworkAvailableChangedListeners.put(callback.asBinder(), callback);
         }
         if (mSatelliteSessionController != null) {
             mSatelliteSessionController.registerForSatelliteModemStateChanged(callback);
@@ -2479,8 +2975,9 @@
                     + " is not initialized yet");
         }
         if (mFeatureFlags.carrierRoamingNbIotNtn()) {
-            plogd("unregisterForModemStateChanged: remove RegistrationFailure Listeners");
+            plogd("unregisterForModemStateChanged: remove Listeners for ModemState");
             mSatelliteRegistrationFailureListeners.remove(callback.asBinder());
+            mTerrestrialNetworkAvailableChangedListeners.remove(callback.asBinder());
         }
     }
 
@@ -2502,7 +2999,7 @@
         }
         plogd("registerForIncomingDatagram: callback=" + callback);
         return mDatagramController.registerForSatelliteDatagram(
-                getHighestPrioritySubscrption(), callback);
+                getSelectedSatelliteSubId(), callback);
     }
 
     /**
@@ -2523,7 +3020,7 @@
         }
         plogd("unregisterForIncomingDatagram: callback=" + callback);
         mDatagramController.unregisterForSatelliteDatagram(
-                getHighestPrioritySubscrption(), callback);
+                getSelectedSatelliteSubId(), callback);
     }
 
     /**
@@ -2545,7 +3042,7 @@
         }
 
         mDatagramController.pollPendingSatelliteDatagrams(
-                getHighestPrioritySubscrption(), result);
+                getSelectedSatelliteSubId(), result);
     }
 
     /**
@@ -2585,7 +3082,7 @@
                     mIsEmergency);
         }
 
-        mDatagramController.sendSatelliteDatagram(getHighestPrioritySubscrption(), datagramType,
+        mDatagramController.sendSatelliteDatagram(getSelectedSatelliteSubId(), datagramType,
                 datagram, needFullScreenPointingUI, result);
     }
 
@@ -2603,10 +3100,11 @@
         }
 
         sendRequestAsync(CMD_GET_TIME_SATELLITE_NEXT_VISIBLE, result, null);
+        incrementResultReceiverCount("SC:requestTimeForNextSatelliteVisibility");
     }
 
     /**
-     * Inform whether the device is aligned with satellite for demo mode.
+     * Inform whether the device is aligned with the satellite in both real and demo mode.
      *
      * @param isAligned {@true} means device is aligned with the satellite, otherwise {@false}.
      */
@@ -2755,6 +3253,7 @@
 
         Phone phone = SatelliteServiceUtils.getPhone();
         sendRequestAsync(CMD_REQUEST_NTN_SIGNAL_STRENGTH, result, phone);
+        incrementResultReceiverCount("SC:requestNtnSignalStrength");
     }
 
     /**
@@ -2873,6 +3372,59 @@
     }
 
     /**
+     * Registers for selected satellite subscription changed event.
+     *
+     * @param callback The callback to handle the selected satellite subscription changed event.
+     *
+     * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
+     */
+    @SatelliteManager.SatelliteResult
+    public int registerForSelectedNbIotSatelliteSubscriptionChanged(
+            @NonNull ISelectedNbIotSatelliteSubscriptionCallback callback) {
+        if (DBG) plogd("registerForSelectedNbIotSatelliteSubscriptionChanged()");
+
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("carrierRoamingNbIotNtn flag is disabled");
+            return SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
+        }
+
+        int error = evaluateOemSatelliteRequestAllowed(false);
+        if (error != SATELLITE_RESULT_SUCCESS) return error;
+
+        mSelectedNbIotSatelliteSubscriptionChangedListeners.put(callback.asBinder(), callback);
+        try {
+            callback.onSelectedNbIotSatelliteSubscriptionChanged(getSelectedSatelliteSubId());
+        } catch (RemoteException ex) {
+            ploge("registerForSelectedNbIotSatelliteSubscriptionChanged: RemoteException ex="
+                    + ex);
+        }
+        return SATELLITE_RESULT_SUCCESS;
+    }
+
+    /**
+     * Unregisters for the selected satellite subscription changed event.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param callback The callback that was passed to {@link
+     *     #registerForSelectedNbIotSatelliteSubscriptionChanged(
+     *     ISelectedNbIotSatelliteSubscriptionCallback)}.
+     */
+    public void unregisterForSelectedNbIotSatelliteSubscriptionChanged(
+            @NonNull ISelectedNbIotSatelliteSubscriptionCallback callback) {
+        if (DBG) plogd("unregisterForSelectedNbIotSatelliteSubscriptionChanged()");
+
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("carrierRoamingNbIotNtn flag is disabled");
+            return;
+        }
+
+        int error = evaluateOemSatelliteRequestAllowed(true);
+        if (error == SATELLITE_RESULT_SUCCESS) {
+            mSelectedNbIotSatelliteSubscriptionChangedListeners.remove(callback.asBinder());
+        }
+    }
+
+    /**
      * This API can be used by only CTS to update satellite vendor service package name.
      *
      * @param servicePackageName The package name of the satellite vendor service.
@@ -2893,8 +3445,8 @@
         synchronized (mIsSatelliteSupportedLock) {
             mIsSatelliteSupported = null;
         }
-        synchronized (mSatelliteViaOemProvisionLock) {
-            mIsSatelliteViaOemProvisioned = Optional.ofNullable(provisioned)
+        synchronized (mDeviceProvisionLock) {
+            mIsDeviceProvisioned = Optional.ofNullable(provisioned)
                     .filter(s -> s.equalsIgnoreCase("true") || s.equalsIgnoreCase("false"))
                     .map(s -> s.equalsIgnoreCase("true"))
                     .orElse(null);
@@ -2930,6 +3482,21 @@
     }
 
     /**
+     * This API can be used by only CTS to control ingoring cellular service state event.
+     *
+     * @param enabled Whether to enable boolean config.
+     * @return {@code true} if the value is set successfully, {@code false} otherwise.
+     */
+    public boolean setSatelliteIgnoreCellularServiceState(boolean enabled) {
+        plogd("setSatelliteIgnoreCellularServiceState - " + enabled);
+        if (mSatelliteSessionController == null) {
+            ploge("setSatelliteIgnoreCellularServiceState is not initialized yet");
+            return false;
+        }
+        return mSatelliteSessionController.setSatelliteIgnoreCellularServiceState(enabled);
+    }
+
+    /**
      * This API can be used by only CTS to override timeout durations used by DatagramController
      * module.
      *
@@ -3008,6 +3575,14 @@
             } else {
                 mDemoPointingNotAlignedDurationMillis = timeoutMillis;
             }
+        } else if (timeoutType
+                == TIMEOUT_TYPE_EVALUATE_ESOS_PROFILES_PRIORITIZATION_DURATION_MILLIS) {
+            if (reset) {
+                mEvaluateEsosProfilesPrioritizationDurationMillis =
+                        getEvaluateEsosProfilesPrioritizationDurationMillis();
+            } else {
+                mEvaluateEsosProfilesPrioritizationDurationMillis = timeoutMillis;
+            }
         } else {
             plogw("Invalid timeoutType=" + timeoutType);
             return false;
@@ -3094,7 +3669,7 @@
             ploge("setOemEnabledSatelliteProvisionStatus: mock modem not allowed");
             return false;
         }
-        synchronized (mSatelliteViaOemProvisionLock) {
+        synchronized (mDeviceProvisionLock) {
             if (reset) {
                 mOverriddenIsSatelliteViaOemProvisioned = null;
             } else {
@@ -3144,15 +3719,18 @@
             plogd("onSatelliteServiceConnected");
             // Vendor service might have just come back from a crash
             moveSatelliteToOffStateAndCleanUpResources(SATELLITE_RESULT_MODEM_ERROR);
+            final String caller = "SC:onSatelliteServiceConnected";
             ResultReceiver receiver = new ResultReceiver(this) {
                 @Override
                 protected void onReceiveResult(
                         int resultCode, Bundle resultData) {
+                    decrementResultReceiverCount(caller);
                     plogd("onSatelliteServiceConnected.requestIsSatelliteSupported:"
                             + " resultCode=" + resultCode);
                 }
             };
             requestIsSatelliteSupported(receiver);
+            incrementResultReceiverCount(caller);
         } else {
             plogd("onSatelliteServiceConnected: Satellite vendor service is not supported."
                     + " Ignored the event");
@@ -3208,6 +3786,36 @@
     }
 
     /**
+     * Notify SMS received.
+     *
+     * @param subId The subId of the subscription used to receive SMS
+     */
+    public void onSmsReceived(int subId) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            logd("onSmsReceived: carrierRoamingNbIotNtn is disabled");
+            return;
+        }
+
+        if (!isSatelliteEnabled()) {
+            logd("onSmsReceived: satellite is not enabled");
+            return;
+        }
+
+        int satelliteSubId = getSelectedSatelliteSubId();
+        if (subId != satelliteSubId) {
+            logd("onSmsReceived: SMS received " + subId
+                    + ", but not satellite subscription " + satelliteSubId);
+            return;
+        }
+
+        if (mDatagramController != null) {
+            mDatagramController.onSmsReceived(subId);
+        } else {
+            logd("onSmsReceived: DatagramController is not initialized");
+        }
+    }
+
+    /**
      * @return {@code true} if satellite is supported via OEM on the device,
      * {@code  false} otherwise.
      */
@@ -3248,12 +3856,20 @@
      * the satellite network {@code plmn}.
      */
     @NonNull
-    public List<Integer> getSupportedSatelliteServices(int subId, String plmn) {
+    public List<Integer> getSupportedSatelliteServicesForPlmn(int subId, String plmn) {
         if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
             logd("getSupportedSatelliteServices: carrierEnabledSatelliteFlag is disabled");
             return new ArrayList<>();
         }
         synchronized (mSupportedSatelliteServicesLock) {
+            Map<String, List<Integer>> allowedServicesList
+                    = mEntitlementServiceTypeMapPerCarrier.get(subId);
+            if (allowedServicesList != null && allowedServicesList.containsKey(plmn)) {
+                List<Integer> allowedServiceValues = allowedServicesList.get(plmn);
+                if (allowedServiceValues != null && !allowedServiceValues.isEmpty()) {
+                    return allowedServiceValues;
+                }
+            }
             if (mSatelliteServicesSupportedByCarriers.containsKey(subId)) {
                 Map<String, Set<Integer>> supportedServices =
                         mSatelliteServicesSupportedByCarriers.get(subId);
@@ -3298,17 +3914,16 @@
             return false;
         }
 
-        synchronized (mSatelliteCapabilitiesLock) {
-            if (mSatelliteCapabilities == null) {
-                ploge("isSatelliteAttachRequired: mSatelliteCapabilities is null");
-                return false;
-            }
-            if (mSatelliteCapabilities.getSupportedRadioTechnologies().contains(
-                    SatelliteManager.NT_RADIO_TECHNOLOGY_NB_IOT_NTN)) {
-                return true;
-            }
+        SatelliteCapabilities satelliteCapabilities = getSatelliteCapabilities();
+        if (satelliteCapabilities == null) {
+            ploge("isSatelliteAttachRequired: mSatelliteCapabilities is null");
             return false;
         }
+        if (satelliteCapabilities.getSupportedRadioTechnologies().contains(
+                SatelliteManager.NT_RADIO_TECHNOLOGY_NB_IOT_NTN)) {
+            return true;
+        }
+        return false;
     }
 
     /**
@@ -3373,28 +3988,30 @@
     }
 
     /**
-     * @return {@code true} if the device is connected to satellite via any carrier within the
+     * @return {@code true} and the corresponding subId if the device is connected to
+     * satellite via any carrier within the
      * {@link CarrierConfigManager#KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT}
-     * duration, {@code false} otherwise.
+     * duration, {@code false} and null otherwise.
      */
-    public boolean isSatelliteConnectedViaCarrierWithinHysteresisTime() {
+    public Pair<Boolean, Integer> isSatelliteConnectedViaCarrierWithinHysteresisTime() {
         if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
             logd("isSatelliteConnectedViaCarrierWithinHysteresisTime: carrierEnabledSatelliteFlag"
                     + " is disabled");
-            return false;
+            return new Pair<>(false, null);
         }
-        if (isUsingNonTerrestrialNetworkViaCarrier().first) {
-            return true;
+        Pair<Boolean, Integer> ntnConnectedState = isUsingNonTerrestrialNetworkViaCarrier();
+        if (ntnConnectedState.first) {
+            return ntnConnectedState;
         }
         for (Phone phone : PhoneFactory.getPhones()) {
             if (isInSatelliteModeForCarrierRoaming(phone)) {
                 logd("isSatelliteConnectedViaCarrierWithinHysteresisTime: "
                         + "subId:" + phone.getSubId()
                         + " is connected to satellite within hysteresis time");
-                return true;
+                return new Pair<>(true, phone.getSubId());
             }
         }
-        return false;
+        return new Pair<>(false, null);
     }
 
     /**
@@ -3416,6 +4033,11 @@
         }
 
         int subId = phone.getSubId();
+        int carrierRoamingNtnConnectType = getCarrierRoamingNtnConnectType(subId);
+        if (carrierRoamingNtnConnectType == CARRIER_ROAMING_NTN_CONNECT_MANUAL) {
+            return isInCarrierRoamingNbIotNtn(phone);
+        }
+
         if (!isSatelliteSupportedViaCarrier(subId)) {
             return false;
         }
@@ -3458,12 +4080,9 @@
      * esos session.
      */
     public boolean shouldTurnOffCarrierSatelliteForEmergencyCall() {
-        synchronized (mSatellitePhoneLock) {
-            if (mSatellitePhone == null) return false;
-            return !mDatagramController.isEmergencyCommunicationEstablished()
-                    && getConfigForSubId(mSatellitePhone.getSubId()).getBoolean(
-                    KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL);
-        }
+        return !mDatagramController.isEmergencyCommunicationEstablished()
+                && getConfigForSubId(getSelectedSatelliteSubId()).getBoolean(
+                KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL);
     }
 
     /**
@@ -3481,6 +4100,14 @@
      * else {@return false}
      */
     public boolean isInCarrierRoamingNbIotNtn() {
+        return isInCarrierRoamingNbIotNtn(getSatellitePhone());
+    }
+
+    /**
+     * @return {@code true} if phone is in carrier roaming nb iot ntn mode,
+     * else {@return false}
+     */
+    private boolean isInCarrierRoamingNbIotNtn(@Nullable Phone phone) {
         if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
             plogd("isInCarrierRoamingNbIotNtn: carrier roaming nb iot ntn "
                     + "feature flag is disabled");
@@ -3492,12 +4119,33 @@
             return false;
         }
 
-        Phone satellitePhone = getSatellitePhone();
-        if (!isCarrierRoamingNtnEligible(satellitePhone)) {
-            plogd("isInCarrierRoamingNbIotNtn: not carrier roaming ntn eligible.");
+        if (phone == null) {
+            plogd("isInCarrierRoamingNbIotNtn: phone is null");
             return false;
         }
 
+        int subId = phone.getSubId();
+        if (!isSatelliteSupportedViaCarrier(subId)) {
+            plogd("isInCarrierRoamingNbIotNtn[phoneId=" + phone.getPhoneId()
+                    + "]: satellite is not supported via carrier");
+            return false;
+        }
+
+        int carrierRoamingNtnConnectType = getCarrierRoamingNtnConnectType(subId);
+        if (carrierRoamingNtnConnectType != CARRIER_ROAMING_NTN_CONNECT_MANUAL) {
+            plogd("isInCarrierRoamingNbIotNtn[phoneId=" + phone.getPhoneId() + "]: not manual "
+                    + "connect. carrierRoamingNtnConnectType = " + carrierRoamingNtnConnectType);
+            return false;
+        }
+
+        if (subId != getSelectedSatelliteSubId()) {
+            plogd("isInCarrierRoamingNbIotNtn: subId=" + subId
+                    + " does not match satellite subId=" + getSelectedSatelliteSubId());
+            return false;
+        }
+
+        plogd("isInCarrierRoamingNbIotNtn: carrier roaming ntn eligible for phone"
+                  + " associated with subId " + phone.getSubId());
         return true;
     }
 
@@ -3566,7 +4214,7 @@
         return DEFAULT_CARRIER_EMERGENCY_CALL_WAIT_FOR_CONNECTION_TIMEOUT_MILLIS;
     }
 
-    private int getCarrierEmergencyCallWaitForConnectionTimeoutMillis(int subId) {
+    public int getCarrierEmergencyCallWaitForConnectionTimeoutMillis(int subId) {
         PersistableBundle config = getPersistableBundle(subId);
         return config.getInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT);
     }
@@ -3608,10 +4256,18 @@
      * @param entitlementEnabled {@code true} Satellite service enabled
      * @param allowedPlmnList    plmn allowed list to use the satellite service
      * @param barredPlmnList    plmn barred list to pass the modem
+     * @param plmnDataPlanMap   data plan map for the plmn
+     * @param plmnServiceTypeMap available services map for the plmn
+     * @param plmnDataServicePolicyMap data service policy map for the plmn
+     * @param plmnVoiceServicePolicyMap voice service policy map for the plmn
      * @param callback           callback for accept
      */
     public void onSatelliteEntitlementStatusUpdated(int subId, boolean entitlementEnabled,
             @Nullable List<String> allowedPlmnList, @Nullable List<String> barredPlmnList,
+            @Nullable Map<String,Integer> plmnDataPlanMap,
+            @Nullable Map<String,List<Integer>> plmnServiceTypeMap,
+            @Nullable Map<String,Integer> plmnDataServicePolicyMap,
+            @Nullable Map<String,Integer> plmnVoiceServicePolicyMap,
             @Nullable IIntegerConsumer callback) {
         if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
             logd("onSatelliteEntitlementStatusUpdated: carrierEnabledSatelliteFlag is not enabled");
@@ -3632,10 +4288,26 @@
         if (barredPlmnList == null) {
             barredPlmnList = new ArrayList<>();
         }
+        if (plmnDataPlanMap == null) {
+            plmnDataPlanMap = new HashMap<>();
+        }
+        if (plmnServiceTypeMap == null) {
+            plmnServiceTypeMap = new HashMap<>();
+        }
+        if (plmnDataServicePolicyMap == null) {
+            plmnDataServicePolicyMap = new HashMap<>();
+        }
+        if (plmnVoiceServicePolicyMap == null) {
+            plmnVoiceServicePolicyMap = new HashMap<>();
+        }
         logd("onSatelliteEntitlementStatusUpdated subId=" + subId + ", entitlementEnabled="
                 + entitlementEnabled + ", allowedPlmnList=["
                 + String.join(",", allowedPlmnList) + "]" + ", barredPlmnList=["
-                + String.join(",", barredPlmnList) + "]");
+                + String.join(",", barredPlmnList) + "]"
+                + ", plmnDataPlanMap =" + plmnDataPlanMap.toString()
+                + ", plmnServiceTypeMap =" + plmnServiceTypeMap.toString()
+                + ", plmnDataServicePolicyMap=" + plmnDataServicePolicyMap.toString()
+                + ", plmnVoiceServicePolicyMap=" + plmnVoiceServicePolicyMap.toString());
 
         synchronized (mSupportedSatelliteServicesLock) {
             if (mSatelliteEntitlementStatusPerCarrier.get(subId, false) != entitlementEnabled) {
@@ -3660,6 +4332,10 @@
                 mMergedPlmnListPerCarrier.remove(subId);
                 mEntitlementPlmnListPerCarrier.put(subId, allowedPlmnList);
                 mEntitlementBarredPlmnListPerCarrier.put(subId, barredPlmnList);
+                mEntitlementDataPlanMapPerCarrier.put(subId, plmnDataPlanMap);
+                mEntitlementServiceTypeMapPerCarrier.put(subId, plmnServiceTypeMap);
+                mEntitlementDataServicePolicyMapPerCarrier.put(subId, plmnDataServicePolicyMap);
+                mEntitlementVoiceServicePolicyMapPerCarrier.put(subId, plmnVoiceServicePolicyMap);
                 updatePlmnListPerCarrier(subId);
                 configureSatellitePlmnForCarrier(subId);
                 mSubscriptionManagerService.setSatelliteEntitlementPlmnList(subId, allowedPlmnList);
@@ -3697,12 +4373,12 @@
      * we will retry the query one more time. Otherwise, we will return the cached result.
      */
     private Boolean isSatelliteSupportedViaOemInternal() {
-        synchronized (mIsSatelliteSupportedLock) {
-            if (mIsSatelliteSupported != null) {
-                /* We have already successfully queried the satellite modem. */
-                return mIsSatelliteSupported;
-            }
+        Boolean isSatelliteSupported = getIsSatelliteSupported();
+        if (isSatelliteSupported != null) {
+            /* We have already successfully queried the satellite modem. */
+            return isSatelliteSupported;
         }
+
         /**
          * We have not successfully checked whether the modem supports satellite service.
          * Thus, we need to retry it now.
@@ -3711,10 +4387,13 @@
                 new ResultReceiver(this) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        decrementResultReceiverCount(
+                                "SC:isSatelliteSupportedViaOemInternal");
                         plogd("isSatelliteSupportedViaOemInternal.requestIsSatelliteSupported:"
                                 + " resultCode=" + resultCode);
                     }
                 });
+        incrementResultReceiverCount("SC:isSatelliteSupportedViaOemInternal");
         return null;
     }
 
@@ -3732,6 +4411,7 @@
                     .setResultCode(SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE)
                     .setIsProvisionRequest(true)
                     .setCarrierId(getSatelliteCarrierId())
+                    .setIsNtnOnlyCarrier(isNtnOnlyCarrier())
                     .reportProvisionMetrics();
             mControllerMetricsStats.reportProvisionCount(
                     SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
@@ -3740,8 +4420,8 @@
         if (result == SATELLITE_RESULT_SUCCESS
                 || result == SATELLITE_RESULT_REQUEST_NOT_SUPPORTED) {
             persistOemEnabledSatelliteProvisionStatus(true);
-            synchronized (mSatelliteViaOemProvisionLock) {
-                mIsSatelliteViaOemProvisioned = true;
+            synchronized (mDeviceProvisionLock) {
+                mIsDeviceProvisioned = true;
             }
             callback.accept(SATELLITE_RESULT_SUCCESS);
             handleEventSatelliteProvisionStateChanged(true);
@@ -3751,6 +4431,7 @@
         mProvisionMetricsStats.setResultCode(result)
                 .setIsProvisionRequest(true)
                 .setCarrierId(getSatelliteCarrierId())
+                .setIsNtnOnlyCarrier(isNtnOnlyCarrier())
                 .reportProvisionMetrics();
         mControllerMetricsStats.reportProvisionCount(result);
     }
@@ -3768,8 +4449,8 @@
         if (result == SATELLITE_RESULT_SUCCESS
                 || result == SATELLITE_RESULT_REQUEST_NOT_SUPPORTED) {
             persistOemEnabledSatelliteProvisionStatus(false);
-            synchronized (mSatelliteViaOemProvisionLock) {
-                mIsSatelliteViaOemProvisioned = false;
+            synchronized (mDeviceProvisionLock) {
+                mIsDeviceProvisioned = false;
             }
             if (arg.callback != null) {
                 arg.callback.accept(SATELLITE_RESULT_SUCCESS);
@@ -3781,6 +4462,7 @@
         mProvisionMetricsStats.setResultCode(result)
                 .setIsProvisionRequest(false)
                 .setCarrierId(getSatelliteCarrierId())
+                .setIsNtnOnlyCarrier(isNtnOnlyCarrier())
                 .reportProvisionMetrics();
         mControllerMetricsStats.reportDeprovisionCount(result);
     }
@@ -3819,21 +4501,21 @@
     }
 
     /**
-     * Check if satellite is provisioned for a subscription on the device.
-     * @return true if satellite is provisioned on the given subscription else return false.
+     * Check if satellite is provisioned for the device.
+     * @return {@code true} if device is provisioned for satellite else return {@code false}.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     @Nullable
-    protected Boolean isSatelliteViaOemProvisioned() {
-        synchronized (mSatelliteViaOemProvisionLock) {
+    protected Boolean isDeviceProvisioned() {
+        synchronized (mDeviceProvisionLock) {
             if (mOverriddenIsSatelliteViaOemProvisioned != null) {
                 return mOverriddenIsSatelliteViaOemProvisioned;
             }
 
-            if (mIsSatelliteViaOemProvisioned == null) {
-                mIsSatelliteViaOemProvisioned = getPersistedOemEnabledSatelliteProvisionStatus();
+            if (mIsDeviceProvisioned == null) {
+                mIsDeviceProvisioned = getPersistedDeviceProvisionStatus();
             }
-            return mIsSatelliteViaOemProvisioned;
+            return mIsDeviceProvisioned;
         }
     }
 
@@ -3841,6 +4523,23 @@
         RequestSatelliteEnabledArgument argument =
                 (RequestSatelliteEnabledArgument) request.argument;
         handlePersistentLoggingOnSessionStart(argument);
+        selectBindingSatelliteSubscription(false);
+        SatelliteModemEnableRequestAttributes enableRequestAttributes =
+                    createModemEnableRequest(argument);
+        if (enableRequestAttributes == null) {
+            plogw("handleSatelliteEnabled: enableRequestAttributes is null");
+            sendErrorAndReportSessionMetrics(
+                    SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE, argument.callback);
+            synchronized (mSatelliteEnabledRequestLock) {
+                if (argument.enableSatellite) {
+                    mSatelliteEnabledRequest = null;
+                } else {
+                    mSatelliteDisabledRequest = null;
+                }
+            }
+            return;
+        }
+
         if (mSatelliteSessionController != null) {
             mSatelliteSessionController.onSatelliteEnablementStarted(argument.enableSatellite);
         } else {
@@ -3860,8 +4559,7 @@
 
         Message onCompleted = obtainMessage(EVENT_SET_SATELLITE_ENABLED_DONE, request);
         mSatelliteModemInterface.requestSatelliteEnabled(
-                createModemEnableRequest(argument),
-                onCompleted);
+                enableRequestAttributes, onCompleted);
         startWaitForSatelliteEnablingResponseTimer(argument);
         // Logs satellite session timestamps for session metrics
         if (argument.enableSatellite) {
@@ -3871,17 +4569,31 @@
     }
 
     /** Get the request attributes that modem needs to enable/disable satellite */
-    private SatelliteModemEnableRequestAttributes createModemEnableRequest(
+    @Nullable private SatelliteModemEnableRequestAttributes createModemEnableRequest(
             @NonNull RequestSatelliteEnabledArgument arg) {
-        int subId = getHighestPrioritySubscrption();
+        int subId = getSelectedSatelliteSubId();
         SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId);
-        String iccid = subInfo != null ? subInfo.getIccId() : "";
-        String apn = getConfigForSubId(subId).getString(KEY_SATELLITE_NIDD_APN_NAME_STRING, "");
+        if (subInfo == null) {
+            loge("createModemEnableRequest: no SubscriptionInfo found for subId=" + subId);
+            return null;
+        }
+        String iccid = subInfo.getIccId();
+        String apn = getNiddApnName(subId);
         return new SatelliteModemEnableRequestAttributes(
                 arg.enableSatellite, arg.enableDemoMode, arg.isEmergency,
                 new SatelliteSubscriptionInfo(iccid, apn));
     }
 
+    @NonNull private String getNiddApnName(int subId) {
+        if (SatelliteServiceUtils.isNtnOnlySubscriptionId(subId)) {
+            String apn = mContext.getResources().getString(R.string.config_satellite_nidd_apn_name);
+            if (!TextUtils.isEmpty(apn)) {
+                return apn;
+            }
+        }
+        return getConfigForSubId(subId).getString(KEY_SATELLITE_NIDD_APN_NAME_STRING, "");
+    }
+
     private void handleRequestSatelliteAttachRestrictionForCarrierCmd(
             SatelliteControllerHandlerRequest request) {
         RequestHandleSatelliteAttachRestrictionForCarrierArgument argument =
@@ -3912,6 +4624,7 @@
             registerForNtnSignalStrengthChanged();
             registerForCapabilitiesChanged();
             registerForSatelliteRegistrationFailure();
+            registerForTerrestrialNetworkAvailableChanged();
 
             requestIsSatelliteProvisioned(
                     new ResultReceiver(this) {
@@ -3919,6 +4632,7 @@
                         protected void onReceiveResult(int resultCode, Bundle resultData) {
                             plogd("updateSatelliteSupportedState.requestIsSatelliteProvisioned: "
                                     + "resultCode=" + resultCode + ", resultData=" + resultData);
+                            decrementResultReceiverCount("SC:requestIsSatelliteProvisioned");
                             requestSatelliteEnabled(false, false, false,
                                     new IIntegerConsumer.Stub() {
                                         @Override
@@ -3929,16 +4643,22 @@
                                     });
                         }
                     });
+            incrementResultReceiverCount("SC:requestIsSatelliteProvisioned");
+
             requestSatelliteCapabilities(
                     new ResultReceiver(this) {
                         @Override
                         protected void onReceiveResult(int resultCode, Bundle resultData) {
                             plogd("updateSatelliteSupportedState.requestSatelliteCapabilities: "
                                     + "resultCode=" + resultCode + ", resultData=" + resultData);
+                            decrementResultReceiverCount("SC:requestSatelliteCapabilities");
                         }
                     });
+            incrementResultReceiverCount("SC:requestSatelliteCapabilities");
         }
         registerForSatelliteSupportedStateChanged();
+        selectBindingSatelliteSubscription(false);
+        notifySatelliteSupportedStateChanged(supported);
     }
 
     private void updateSatelliteEnabledState(boolean enabled, String caller) {
@@ -3954,6 +4674,9 @@
         if (!enabled) {
             mIsModemEnabledReportingNtnSignalStrength.set(false);
         }
+        if (mFeatureFlags.satelliteStateChangeListener()) {
+            notifyEnabledStateChanged(enabled);
+        }
     }
 
     private void registerForPendingDatagramCount() {
@@ -4026,14 +4749,27 @@
         }
     }
 
+    private void registerForTerrestrialNetworkAvailableChanged() {
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            if (!mRegisteredForTerrestrialNetworkAvailableChanged.get()) {
+                mSatelliteModemInterface.registerForTerrestrialNetworkAvailableChanged(this,
+                        EVENT_TERRESTRIAL_NETWORK_AVAILABLE_CHANGED, null);
+                mRegisteredForTerrestrialNetworkAvailableChanged.set(true);
+            }
+        }
+    }
+
     private void handleEventSatelliteProvisionStateChanged(boolean provisioned) {
         plogd("handleSatelliteProvisionStateChangedEvent: provisioned=" + provisioned);
 
-        synchronized (mSatelliteViaOemProvisionLock) {
+        synchronized (mDeviceProvisionLock) {
             persistOemEnabledSatelliteProvisionStatus(provisioned);
-            mIsSatelliteViaOemProvisioned = provisioned;
+            mIsDeviceProvisioned = provisioned;
         }
+        notifyDeviceProvisionStateChanged(provisioned);
+    }
 
+    private void notifyDeviceProvisionStateChanged(boolean provisioned) {
         List<ISatelliteProvisionStateCallback> deadCallersList = new ArrayList<>();
         mSatelliteProvisionStateChangedListeners.values().forEach(listener -> {
             try {
@@ -4048,48 +4784,77 @@
         });
     }
 
-    private void handleEventSatelliteSubscriptionProvisionStateChanged(
-            List<SatelliteSubscriberInfo> newList, boolean provisioned) {
-        logd("handleEventSatelliteSubscriptionProvisionStateChanged: newList=" + newList
-                + " , provisioned=" + provisioned);
+    private boolean updateSatelliteSubscriptionProvisionState(List<SatelliteSubscriberInfo> newList,
+            boolean provisioned) {
+        logd("updateSatelliteSubscriptionProvisionState: List=" + newList + " , provisioned="
+                + provisioned);
         boolean provisionChanged = false;
         synchronized (mSatelliteTokenProvisionedLock) {
             for (SatelliteSubscriberInfo subscriberInfo : newList) {
-                if (mProvisionedSubscriberId.getOrDefault(subscriberInfo.getSubscriberId(), false)
-                        == provisioned) {
+
+                int subId = subscriberInfo.getSubId();
+                Boolean currentProvisioned =
+                        mProvisionedSubscriberId.get(subscriberInfo.getSubscriberId());
+                if (currentProvisioned == null) {
+                    currentProvisioned = false;
+                }
+
+                Boolean isProvisionedInPersistentDb = false;
+                try {
+                    isProvisionedInPersistentDb = mSubscriptionManagerService
+                         .isSatelliteProvisionedForNonIpDatagram(subId);
+                    if (isProvisionedInPersistentDb == null) {
+                        isProvisionedInPersistentDb = false;
+                    }
+                } catch (IllegalArgumentException | SecurityException ex) {
+                    ploge("isSatelliteProvisionedForNonIpDatagram: subId=" + subId + ", ex="
+                            + ex);
+                }
+                if (currentProvisioned == provisioned
+                        && isProvisionedInPersistentDb == provisioned) {
                     continue;
                 }
                 provisionChanged = true;
                 mProvisionedSubscriberId.put(subscriberInfo.getSubscriberId(), provisioned);
-                int subId = mSubscriberIdPerSub.getOrDefault(subscriberInfo.getSubscriberId(),
-                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
                 try {
                     mSubscriptionManagerService.setIsSatelliteProvisionedForNonIpDatagram(subId,
                             provisioned);
-                    plogd("handleEventSatelliteSubscriptionProvisionStateChanged: set Provision "
-                            + "state to db subId=" + subId);
+                    plogd("updateSatelliteSubscriptionProvisionState: set Provision state to db "
+                            + "subId=" + subId);
                 } catch (IllegalArgumentException | SecurityException ex) {
                     ploge("setIsSatelliteProvisionedForNonIpDatagram: subId=" + subId + ", ex="
                             + ex);
                 }
             }
         }
-        if (!provisionChanged) {
-            logd("handleEventSatelliteSubscriptionProvisionStateChanged: provision state nothing "
-                    + "changed.");
-            return;
-        }
+        return provisionChanged;
+    }
+
+    private void handleEventSatelliteSubscriptionProvisionStateChanged() {
         List<SatelliteSubscriberProvisionStatus> informList =
                 getPrioritizedSatelliteSubscriberProvisionStatusList();
         plogd("handleEventSatelliteSubscriptionProvisionStateChanged: " + informList);
         notifySatelliteSubscriptionProvisionStateChanged(informList);
-        // Report updated provisioned status
+        updateDeviceProvisionStatus();
+        // Report updated provisioned status to metrics.
         synchronized (mSatelliteTokenProvisionedLock) {
             boolean isProvisioned = !mProvisionedSubscriberId.isEmpty()
                     && mProvisionedSubscriberId.containsValue(Boolean.TRUE);
             mControllerMetricsStats.setIsProvisioned(isProvisioned);
         }
-        handleStateChangedForCarrierRoamingNtnEligibility();
+        selectBindingSatelliteSubscription(false);
+        evaluateCarrierRoamingNtnEligibilityChange();
+    }
+
+    private void updateDeviceProvisionStatus() {
+        boolean isProvisioned = getPersistedDeviceProvisionStatus();
+        plogd("updateDeviceProvisionStatus: isProvisioned=" + isProvisioned);
+        synchronized (mDeviceProvisionLock) {
+            if (mIsDeviceProvisioned == null || mIsDeviceProvisioned != isProvisioned) {
+                mIsDeviceProvisioned = isProvisioned;
+                notifyDeviceProvisionStateChanged(isProvisioned);
+            }
+        }
     }
 
     private void notifySatelliteSubscriptionProvisionStateChanged(
@@ -4111,28 +4876,33 @@
     private void handleEventSatelliteModemStateChanged(
             @SatelliteManager.SatelliteModemState int state) {
         plogd("handleEventSatelliteModemStateChanged: state=" + state);
+
+        synchronized (mSatelliteModemStateLock) {
+            mSatelliteModemState = state;
+        }
+
         if (state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE
                 || state == SatelliteManager.SATELLITE_MODEM_STATE_OFF) {
+            if (!isWaitingForDisableSatelliteModemResponse()) {
+                moveSatelliteToOffStateAndCleanUpResources(SATELLITE_RESULT_SUCCESS);
+            } else {
+                notifyModemStateChangedToSessionController(
+                        SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+            }
+
             synchronized (mSatelliteEnabledRequestLock) {
-                if (!mWaitingForDisableSatelliteModemResponse) {
-                    moveSatelliteToOffStateAndCleanUpResources(
-                            SATELLITE_RESULT_SUCCESS);
-                } else {
-                    notifyModemStateChangedToSessionController(
-                            SatelliteManager.SATELLITE_MODEM_STATE_OFF);
-                }
                 mWaitingForSatelliteModemOff = false;
             }
         } else {
-            if (isSatelliteEnabled() || isSatelliteBeingEnabled() || isSatelliteBeingDisabled()) {
+            if (isSatelliteEnabledOrBeingEnabled() || isSatelliteBeingDisabled()) {
                 notifyModemStateChangedToSessionController(state);
             } else {
-                // Telephony framework and modem are out of sync. We need to disable modem
+                // Telephony framework and modem are out of sync. We need to disable
                 synchronized (mSatelliteEnabledRequestLock) {
                     plogw("Satellite modem is in a bad state. Disabling satellite modem now ...");
                     Consumer<Integer> result = integer -> plogd(
                             "handleEventSatelliteModemStateChanged: disabling satellite result="
-                            + integer);
+                                    + integer);
                     mSatelliteDisabledRequest = new RequestSatelliteEnabledArgument(
                             false /* enableSatellite */, false /* enableDemoMode */,
                             false /* isEmergency */, result);
@@ -4188,11 +4958,13 @@
         synchronized (mSatelliteCapabilitiesLock) {
             mSatelliteCapabilities = capabilities;
         }
+        overrideSatelliteCapabilitiesIfApplicable();
 
+        SatelliteCapabilities satelliteCapabilities = getSatelliteCapabilities();
         List<ISatelliteCapabilitiesCallback> deadCallersList = new ArrayList<>();
         mSatelliteCapabilitiesChangedListeners.values().forEach(listener -> {
             try {
-                listener.onSatelliteCapabilitiesChanged(capabilities);
+                listener.onSatelliteCapabilitiesChanged(satelliteCapabilities);
             } catch (RemoteException e) {
                 plogd("handleEventSatelliteCapabilitiesChanged RemoteException: " + e);
                 deadCallersList.add(listener);
@@ -4206,38 +4978,64 @@
     private void handleEventSatelliteSupportedStateChanged(boolean supported) {
         plogd("handleSatelliteSupportedStateChangedEvent: supported=" + supported);
 
-        synchronized (mIsSatelliteSupportedLock) {
-            if (mIsSatelliteSupported != null && mIsSatelliteSupported == supported) {
-                if (DBG) {
-                    plogd("current satellite support state and new supported state are matched,"
-                            + " ignore update.");
-                }
-                return;
+        Boolean isSatelliteSupported = getIsSatelliteSupported();
+        if (isSatelliteSupported != null && isSatelliteSupported == supported) {
+            if (DBG) {
+                plogd("current satellite support state and new supported state are matched,"
+                        + " ignore update.");
             }
-
-            updateSatelliteSupportedState(supported);
-
-            /* In case satellite has been reported as not support from modem, but satellite is
-               enabled, request disable satellite. */
-            synchronized (mIsSatelliteEnabledLock) {
-                if (!supported && mIsSatelliteEnabled != null && mIsSatelliteEnabled) {
-                    plogd("Invoke requestSatelliteEnabled(), supported=false, "
-                            + "mIsSatelliteEnabled=true");
-                    requestSatelliteEnabled(false /* enableSatellite */, false /* enableDemoMode */,
-                            false /* isEmergency */,
-                            new IIntegerConsumer.Stub() {
-                                @Override
-                                public void accept(int result) {
-                                    plogd("handleSatelliteSupportedStateChangedEvent: request "
-                                            + "satellite disable, result=" + result);
-                                }
-                            });
-
-                }
-            }
-            mIsSatelliteSupported = supported;
+            return;
         }
 
+        updateSatelliteSupportedState(supported);
+
+        Boolean isSatelliteEnabled = getIsSatelliteEnabled();
+         /* In case satellite has been reported as not support from modem, but satellite is
+               enabled, request disable satellite. */
+        if (!supported && isSatelliteEnabled != null && isSatelliteEnabled) {
+            plogd("Invoke requestSatelliteEnabled(), supported=false, "
+                    + "mIsSatelliteEnabled=true");
+            requestSatelliteEnabled(false /* enableSatellite */, false /* enableDemoMode */,
+                    false /* isEmergency */,
+                    new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            plogd("handleSatelliteSupportedStateChangedEvent: request "
+                                    + "satellite disable, result=" + result);
+                        }
+                    });
+
+        }
+
+        synchronized (mIsSatelliteSupportedLock) {
+            mIsSatelliteSupported = supported;
+        }
+    }
+
+    private void handleEventSelectedNbIotSatelliteSubscriptionChanged(int selectedSubId) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("handleEventSelectedNbIotSatelliteSubscriptionChanged: "
+                    + "carrierRoamingNbIotNtn flag is disabled");
+            return;
+        }
+
+        plogd("handleEventSelectedNbIotSatelliteSubscriptionChanged: " + selectedSubId);
+
+        List<ISelectedNbIotSatelliteSubscriptionCallback> deadCallersList = new ArrayList<>();
+        mSelectedNbIotSatelliteSubscriptionChangedListeners.values().forEach(listener -> {
+            try {
+                listener.onSelectedNbIotSatelliteSubscriptionChanged(selectedSubId);
+            } catch (RemoteException e) {
+                logd("handleEventSelectedNbIotSatelliteSubscriptionChanged RemoteException: " + e);
+                deadCallersList.add(listener);
+            }
+        });
+        deadCallersList.forEach(listener -> {
+            mSelectedNbIotSatelliteSubscriptionChangedListeners.remove(listener.asBinder());
+        });
+    }
+
+    private void notifySatelliteSupportedStateChanged(boolean supported) {
         List<ISatelliteSupportedStateCallback> deadCallersList = new ArrayList<>();
         mSatelliteSupportedStateChangedListeners.values().forEach(listener -> {
             try {
@@ -4274,7 +5072,8 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected void setSettingsKeyToAllowDeviceRotation(int val) {
         // Only allows on a foldable device type.
-        if (!isFoldable(mContext)) {
+        if (!isFoldable(mContext, mDeviceStates)) {
+            logd("setSettingsKeyToAllowDeviceRotation(" + val + "), device was not a foldable");
             return;
         }
 
@@ -4315,10 +5114,19 @@
      * If the device type is foldable.
      *
      * @param context context
+     * @param deviceStates list of {@link DeviceState}s provided from {@link DeviceStateManager}
      * @return {@code true} if device type is foldable. {@code false} for otherwise.
      */
-    private boolean isFoldable(Context context) {
-        return context.getResources().getIntArray(R.array.config_foldedDeviceStates).length > 0;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean isFoldable(Context context, List<DeviceState> deviceStates) {
+        if (android.hardware.devicestate.feature.flags.Flags.deviceStatePropertyMigration()) {
+            return deviceStates.stream().anyMatch(deviceState -> deviceState.hasProperty(
+                    PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY)
+                    || deviceState.hasProperty(
+                    PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY));
+        } else {
+            return context.getResources().getIntArray(R.array.config_foldedDeviceStates).length > 0;
+        }
     }
 
     /**
@@ -4405,6 +5213,7 @@
                     sendRequestAsync(CMD_UPDATE_SATELLITE_ENABLE_ATTRIBUTES,
                             mSatelliteEnableAttributesUpdateRequest, null);
                 }
+                updateLastNotifiedNtnModeAndNotify(getSatellitePhone());
             }
         }
     }
@@ -4435,22 +5244,28 @@
     public void moveSatelliteToOffStateAndCleanUpResources(
             @SatelliteManager.SatelliteResult int resultCode) {
         plogd("moveSatelliteToOffStateAndCleanUpResources");
+        setDemoModeEnabled(false);
+        handlePersistentLoggingOnSessionEnd(mIsEmergency);
+        setEmergencyMode(false);
         synchronized (mIsSatelliteEnabledLock) {
-            setDemoModeEnabled(false);
-            handlePersistentLoggingOnSessionEnd(mIsEmergency);
-            setEmergencyMode(false);
             mIsSatelliteEnabled = false;
-            setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_FALSE);
-            setSettingsKeyToAllowDeviceRotation(SATELLITE_MODE_ENABLED_FALSE);
-            abortSatelliteDisableRequest(resultCode);
-            abortSatelliteEnableRequest(resultCode);
-            abortSatelliteEnableAttributesUpdateRequest(resultCode);
-            resetSatelliteEnabledRequest();
-            resetSatelliteDisabledRequest();
-            // TODO (b/361139260): Stop timer to wait for other radios off
-            updateSatelliteEnabledState(
-                    false, "moveSatelliteToOffStateAndCleanUpResources");
         }
+        setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_FALSE);
+        setSettingsKeyToAllowDeviceRotation(SATELLITE_MODE_ENABLED_FALSE);
+        abortSatelliteDisableRequest(resultCode);
+        abortSatelliteEnableRequest(resultCode);
+        abortSatelliteEnableAttributesUpdateRequest(resultCode);
+        resetSatelliteEnabledRequest();
+        resetSatelliteDisabledRequest();
+        // TODO (b/361139260): Stop timer to wait for other radios off
+        updateSatelliteEnabledState(
+                false, "moveSatelliteToOffStateAndCleanUpResources");
+        selectBindingSatelliteSubscription(false);
+        updateLastNotifiedNtnModeAndNotify(getSatellitePhone());
+
+        sendMessage(obtainMessage(CMD_EVALUATE_ESOS_PROFILES_PRIORITIZATION));
+        // Evaluate eligibility after satellite session is disabled
+        sendMessage(obtainMessage(CMD_EVALUATE_CARRIER_ROAMING_NTN_ELIGIBILITY_CHANGE));
     }
 
     private void setDemoModeEnabled(boolean enabled) {
@@ -4513,6 +5328,7 @@
             if (activeSubIds != null) {
                 for (int subId : activeSubIds) {
                     updateSupportedSatelliteServices(subId);
+                    handleCarrierRoamingNtnAvailableServicesChanged(subId);
                 }
             } else {
                 loge("updateSupportedSatelliteServicesForActiveSubscriptions: "
@@ -4625,28 +5441,44 @@
                         KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE));
     }
 
+    @NonNull
+    private Map<String, Set<Integer>> readRegionalSatelliteEarfcnsFromCarrierConfig(int subId) {
+        PersistableBundle config = getPersistableBundle(subId);
+        return SatelliteServiceUtils.parseRegionalSatelliteEarfcns(
+                config.getPersistableBundle(KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE));
+    }
+
     @NonNull private PersistableBundle getConfigForSubId(int subId) {
         PersistableBundle config = null;
         if (mCarrierConfigManager != null) {
-            config = mCarrierConfigManager.getConfigForSubId(subId,
-                    KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
-                    KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
-                    KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL,
-                    KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT,
-                    KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL,
-                    KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY,
-                    KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL,
-                    KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
-                    KEY_SATELLITE_ESOS_SUPPORTED_BOOL,
-                    KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL,
-                    KEY_SATELLITE_NIDD_APN_NAME_STRING,
-                    KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT,
-                    KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT,
-                    KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT,
-                    KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT,
-                    KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT,
-                    KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT
-            );
+            try {
+                config = mCarrierConfigManager.getConfigForSubId(subId,
+                        KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                        KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                        KEY_SATELLITE_DISPLAY_NAME_STRING,
+                        KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL,
+                        KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT,
+                        KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL,
+                        KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY,
+                        KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL,
+                        KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
+                        KEY_SATELLITE_ESOS_SUPPORTED_BOOL,
+                        KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL,
+                        KEY_SATELLITE_NIDD_APN_NAME_STRING,
+                        KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT,
+                        KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT,
+                        KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT,
+                        KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT,
+                        KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT,
+                        KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT,
+                        KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE_BYTES_INT,
+                        KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY,
+                        KEY_REGIONAL_SATELLITE_EARFCN_BUNDLE,
+                        KEY_SATELLITE_DATA_SUPPORT_MODE_INT
+                );
+            } catch (Exception e) {
+                logw("getConfigForSubId: " + e);
+            }
         }
         if (config == null || config.isEmpty()) {
             config = CarrierConfigManager.getDefaultConfig();
@@ -4670,15 +5502,16 @@
         updateSupportedSatelliteServicesForActiveSubscriptions();
         processNewCarrierConfigData(subId);
         resetCarrierRoamingSatelliteModeParams(subId);
-        handleStateChangedForCarrierRoamingNtnEligibility();
+        evaluateCarrierRoamingNtnEligibilityChange();
         sendMessageDelayed(obtainMessage(CMD_EVALUATE_ESOS_PROFILES_PRIORITIZATION),
-                TimeUnit.MINUTES.toMillis(1));
+                mEvaluateEsosProfilesPrioritizationDurationMillis);
+        updateRegionalSatelliteEarfcns(subId);
     }
 
     // imsi, msisdn, default sms subId change
     private void handleSubscriptionsChanged() {
         sendMessageDelayed(obtainMessage(CMD_EVALUATE_ESOS_PROFILES_PRIORITIZATION),
-                TimeUnit.MINUTES.toMillis(1));
+                mEvaluateEsosProfilesPrioritizationDurationMillis);
     }
 
     private void processNewCarrierConfigData(int subId) {
@@ -4760,7 +5593,7 @@
 
     /** If the provision state per subscriberId for the cached is not exist, check the database for
      * the corresponding value and use it. */
-    private void updateSatelliteProvisionedStatePerSubscriberId() {
+    protected void updateSatelliteProvisionedStatePerSubscriberId() {
         if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
             return;
         }
@@ -4784,6 +5617,8 @@
                 }
             }
         }
+        // Need to update the provision status of the device
+        updateDeviceProvisionStatus();
     }
 
     @NonNull
@@ -4825,6 +5660,17 @@
         return getConfigForSubId(subId).getBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL);
     }
 
+    /**
+     * Return whether the device allows to turn off satellite session for emergency call.
+     *
+     * @param subId Associated subscription ID
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean turnOffSatelliteSessionForEmergencyCall(int subId) {
+        return getConfigForSubId(subId).getBoolean(
+                KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL);
+    }
+
     private int getCarrierRoamingNtnConnectType(int subId) {
         return getConfigForSubId(subId).getInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT);
     }
@@ -4834,6 +5680,11 @@
                 KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT);
     }
 
+    @CarrierConfigManager.SATELLITE_DATA_SUPPORT_MODE
+    private int getCarrierSatelliteDataSupportedMode(int subId) {
+        return getConfigForSubId(subId).getInt(KEY_SATELLITE_DATA_SUPPORT_MODE_INT);
+    }
+
     /**
      * Check if satellite attach is enabled by user for the carrier associated with the
      * {@code subId}.
@@ -5086,7 +5937,7 @@
         }
 
         if (isProvisionRequired) {
-            Boolean satelliteProvisioned = isSatelliteViaOemProvisioned();
+            Boolean satelliteProvisioned = isDeviceProvisioned();
             if (satelliteProvisioned == null) {
                 plogd("evaluateOemSatelliteRequestAllowed: satelliteProvisioned is null");
                 return SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
@@ -5106,13 +5957,23 @@
      */
     @VisibleForTesting
     protected @SatelliteManager.NTRadioTechnology int getSupportedNtnRadioTechnology() {
-        synchronized (mSatelliteCapabilitiesLock) {
-            if (mSatelliteCapabilities != null) {
-                return mSatelliteCapabilities.getSupportedRadioTechnologies()
-                        .stream().findFirst().orElse(SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN);
-            }
-            return SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN;
+        SatelliteCapabilities satelliteCapabilities = getSatelliteCapabilities();
+        if (satelliteCapabilities != null) {
+            return satelliteCapabilities.getSupportedRadioTechnologies()
+                    .stream().findFirst().orElse(SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN);
         }
+        return SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN;
+    }
+
+    /**
+     * Returns a list of messaging apps that support satellite.
+     */
+    @NonNull public List<String> getSatelliteSupportedMsgApps(int subId) {
+        String[] satelliteSupportedMsgApps = getConfigForSubId(subId)
+                .getStringArray(KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY);
+
+        return satelliteSupportedMsgApps != null
+                ? List.of(satelliteSupportedMsgApps) : Collections.emptyList();
     }
 
     private void sendErrorAndReportSessionMetrics(@SatelliteManager.SatelliteResult int error,
@@ -5122,11 +5983,21 @@
                 .setSatelliteTechnology(getSupportedNtnRadioTechnology())
                 .setIsDemoMode(mIsDemoModeEnabled)
                 .setCarrierId(getSatelliteCarrierId())
+                .setIsNtnOnlyCarrier(isNtnOnlyCarrier())
                 .reportSessionMetrics();
         mSessionStartTimeStamp = 0;
         mSessionProcessingTimeStamp = 0;
     }
 
+    public boolean isNtnOnlyCarrier() {
+        synchronized (mSatelliteTokenProvisionedLock) {
+            if (mSelectedSatelliteSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                return false;
+            }
+            return mSelectedSatelliteSubId == getNtnOnlySubscriptionId();
+        }
+    }
+
     private void registerForServiceStateChanged() {
         if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
             return;
@@ -5136,8 +6007,15 @@
         }
     }
 
+    private void registerForSignalStrengthChanged() {
+        for (Phone phone : PhoneFactory.getPhones()) {
+            phone.getSignalStrengthController().registerForSignalStrengthChanged(this,
+                    EVENT_SIGNAL_STRENGTH_CHANGED, phone.getPhoneId());
+        }
+    }
+
     private void handleEventServiceStateChanged() {
-        handleStateChangedForCarrierRoamingNtnEligibility();
+        evaluateCarrierRoamingNtnEligibilityChange();
         handleServiceStateForSatelliteConnectionViaCarrier();
     }
 
@@ -5201,6 +6079,7 @@
                     mWasSatelliteConnectedViaCarrier.put(subId, false);
                 }
                 updateLastNotifiedNtnModeAndNotify(phone);
+                updateLastNotifiedCarrierRoamingNtnSignalStrengthAndNotify(phone);
             }
         }
         determineAutoConnectSystemNotification();
@@ -5221,6 +6100,7 @@
                 if (!initialized) mInitialized.put(subId, true);
                 mLastNotifiedNtnMode.put(subId, currNtnMode);
                 phone.notifyCarrierRoamingNtnModeChanged(currNtnMode);
+                updateLastNotifiedCarrierRoamingNtnSignalStrengthAndNotify(phone);
                 logCarrierRoamingSatelliteSessionStats(phone, lastNotifiedNtnMode, currNtnMode);
                 if(mIsNotificationShowing && !currNtnMode) {
                     dismissSatelliteNotification();
@@ -5249,27 +6129,32 @@
         }
     }
 
-    private void handleStateChangedForCarrierRoamingNtnEligibility() {
+    private void evaluateCarrierRoamingNtnEligibilityChange() {
         if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
-            plogd("handleStateChangedForCarrierRoamingNtnEligibility: "
+            plogd("evaluateCarrierRoamingNtnEligibilityChange: "
                     + "carrierRoamingNbIotNtn flag is disabled");
             return;
         }
 
-        synchronized (mSatellitePhoneLock) {
-            boolean eligible = isCarrierRoamingNtnEligible(mSatellitePhone);
-            plogd("handleStateChangedForCarrierRoamingNtnEligibility: "
-                    + "isCarrierRoamingNtnEligible=" + eligible);
+        registerForSatelliteCommunicationAllowedStateChanged();
 
-            if (eligible) {
-                if (shouldStartNtnEligibilityHysteresisTimer(eligible)) {
-                    startNtnEligibilityHysteresisTimer();
-                }
-            } else {
-                mNtnEligibilityHysteresisTimedOut = false;
-                stopNtnEligibilityHysteresisTimer();
-                updateLastNotifiedNtnEligibilityAndNotify(false);
+        if (isSatelliteEnabledOrBeingEnabled()) {
+            plogd("evaluateCarrierRoamingNtnEligibilityChange: "
+                    + "Skip eligibility check as satellite is enabled or being enabled");
+            return;
+        }
+
+        boolean eligible = isCarrierRoamingNtnEligible(getSatellitePhone());
+        plogd("evaluateCarrierRoamingNtnEligibilityChange: "
+                + "isCarrierRoamingNtnEligible=" + eligible);
+
+        if (eligible) {
+            if (shouldStartNtnEligibilityHysteresisTimer(eligible)) {
+                startNtnEligibilityHysteresisTimer();
             }
+        } else {
+            stopNtnEligibilityHysteresisTimer();
+            updateLastNotifiedNtnEligibilityAndNotify(false);
         }
     }
 
@@ -5293,20 +6178,19 @@
     }
 
     private void startNtnEligibilityHysteresisTimer() {
-        synchronized (mSatellitePhoneLock) {
-            if (mSatellitePhone == null) {
-                ploge("startNtnEligibilityHysteresisTimer: mSatellitePhone is null.");
-                return;
-            }
-
-            int subId = getHighestPrioritySubscrption();
-            long timeout = getCarrierSupportedSatelliteNotificationHysteresisTimeMillis(subId);
-            mNtnEligibilityHysteresisTimedOut = false;
-            plogd("startNtnEligibilityHysteresisTimer: sendMessageDelayed subId=" + subId
-                    + ", phoneId=" + mSatellitePhone.getPhoneId() + ", timeout=" + timeout);
-            sendMessageDelayed(obtainMessage(EVENT_NOTIFY_NTN_ELIGIBILITY_HYSTERESIS_TIMED_OUT),
-                    timeout);
+        Phone satellitePhone = getSatellitePhone();
+        if (satellitePhone == null) {
+            ploge("startNtnEligibilityHysteresisTimer: mSatellitePhone is null.");
+            return;
         }
+
+        int subId = getSelectedSatelliteSubId();
+        long timeout = getCarrierSupportedSatelliteNotificationHysteresisTimeMillis(subId);
+        plogd("startNtnEligibilityHysteresisTimer: sendMessageDelayed subId=" + subId
+                    + ", phoneId=" + satellitePhone.getPhoneId() + ", timeout=" + timeout);
+        sendMessageDelayed(obtainMessage(EVENT_NOTIFY_NTN_ELIGIBILITY_HYSTERESIS_TIMED_OUT),
+                timeout);
+
     }
 
     private void stopNtnEligibilityHysteresisTimer() {
@@ -5321,19 +6205,28 @@
             return;
         }
 
-        synchronized (mSatellitePhoneLock) {
-            if (mSatellitePhone == null) {
-                ploge("notifyNtnEligibility: mSatellitePhone is null");
-                return;
-            }
+        Phone satellitePhone = getSatellitePhone();
+        if (satellitePhone == null) {
+            ploge("notifyNtnEligibility: mSatellitePhone is null");
+            return;
+        }
 
-            plogd("notifyNtnEligibility: phoneId=" + mSatellitePhone.getPhoneId()
+        if (mOverrideNtnEligibility != null) {
+            satellitePhone.notifyCarrierRoamingNtnEligibleStateChanged(currentNtnEligibility);
+            mControllerMetricsStats.reportP2PSmsEligibilityNotificationsCount(
+                    currentNtnEligibility);
+            return;
+        }
+
+        int selectedSatelliteSubId = getSelectedSatelliteSubId();
+        synchronized (mSatellitePhoneLock) {
+            plogd("notifyNtnEligibility: phoneId=" + satellitePhone.getPhoneId()
                     + " currentNtnEligibility=" + currentNtnEligibility);
             if (mLastNotifiedNtnEligibility == null
                     || mLastNotifiedNtnEligibility != currentNtnEligibility) {
                 mLastNotifiedNtnEligibility = currentNtnEligibility;
-                mSatellitePhone.notifyCarrierRoamingNtnEligibleStateChanged(currentNtnEligibility);
-                updateSatelliteSystemNotification(getHighestPrioritySubscrption(),
+                satellitePhone.notifyCarrierRoamingNtnEligibleStateChanged(currentNtnEligibility);
+                updateSatelliteSystemNotification(selectedSatelliteSubId,
                         CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_MANUAL,
                         currentNtnEligibility);
             }
@@ -5353,10 +6246,10 @@
     }
 
     private void persistOemEnabledSatelliteProvisionStatus(boolean isProvisioned) {
-        synchronized (mSatelliteViaOemProvisionLock) {
+        synchronized (mDeviceProvisionLock) {
             plogd("persistOemEnabledSatelliteProvisionStatus: isProvisioned=" + isProvisioned);
             if (mFeatureFlags.carrierRoamingNbIotNtn()) {
-                int subId = SatelliteServiceUtils.getNtnOnlySubscriptionId(mContext);
+                int subId = getNtnOnlySubscriptionId();
                 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
                     try {
                         mSubscriptionManagerService.setIsSatelliteProvisionedForNonIpDatagram(subId,
@@ -5383,24 +6276,34 @@
     }
 
     @Nullable
-    private Boolean getPersistedOemEnabledSatelliteProvisionStatus() {
-        plogd("getPersistedOemEnabledSatelliteProvisionStatus:");
-        synchronized (mSatelliteViaOemProvisionLock) {
+    private boolean getPersistedDeviceProvisionStatus() {
+        plogd("getPersistedDeviceProvisionStatus");
+        synchronized (mDeviceProvisionLock) {
             if (mFeatureFlags.carrierRoamingNbIotNtn()) {
-                int subId = SatelliteServiceUtils.getNtnOnlySubscriptionId(mContext);
+                int subId = getNtnOnlySubscriptionId();
                 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-                    return mSubscriptionManagerService.isSatelliteProvisionedForNonIpDatagram(
-                            subId);
-                } else {
-                    plogd("getPersistedOemEnabledSatelliteProvisionStatus: "
-                            + "subId=INVALID_SUBSCRIPTION_ID, return null");
-                    return null;
+                    if (mSubscriptionManagerService.isSatelliteProvisionedForNonIpDatagram(subId)) {
+                        return true;
+                    }
                 }
+
+                List<SubscriptionInfo> activeSubscriptionInfoList =
+                        mSubscriptionManagerService.getActiveSubscriptionInfoList(
+                            mContext.getOpPackageName(), mContext.getAttributionTag(), true);
+                for (SubscriptionInfo info : activeSubscriptionInfoList) {
+                    if (info.isSatelliteESOSSupported()) {
+                        if (mSubscriptionManagerService.isSatelliteProvisionedForNonIpDatagram(
+                                info.getSubscriptionId())) {
+                            return true;
+                        }
+                    }
+                }
+                return false;
             } else {
                 if (!loadSatelliteSharedPreferences()) return false;
 
                 if (mSharedPreferences == null) {
-                    ploge("getPersistedOemEnabledSatelliteProvisionStatus: mSharedPreferences is "
+                    ploge("getPersistedDeviceProvisionStatus: mSharedPreferences is "
                             + "null");
                     return false;
                 } else {
@@ -5432,8 +6335,9 @@
 
         Bundle bundle = new Bundle();
         bundle.putBoolean(SatelliteManager.KEY_SATELLITE_PROVISIONED,
-                Boolean.TRUE.equals(isSatelliteViaOemProvisioned()));
+                Boolean.TRUE.equals(isDeviceProvisioned()));
         ((ResultReceiver) request.argument).send(SATELLITE_RESULT_SUCCESS, bundle);
+        decrementResultReceiverCount("SC:requestIsSatelliteProvisioned");
     }
 
     private long getWaitForSatelliteEnablingResponseTimeoutMillis() {
@@ -5550,50 +6454,51 @@
                 + argument.requestId + ", enableSatellite=" + argument.enableSatellite);
 
         argument.callback.accept(SATELLITE_RESULT_MODEM_TIMEOUT);
-        synchronized (mIsSatelliteEnabledLock) {
-            if (argument.enableSatellite) {
-                resetSatelliteEnabledRequest();
-                abortSatelliteEnableAttributesUpdateRequest(SATELLITE_RESULT_REQUEST_ABORTED);
-                synchronized (mSatelliteEnabledRequestLock) {
-                    if (mSatelliteDisabledRequest == null) {
-                        IIntegerConsumer callback = new IIntegerConsumer.Stub() {
-                            @Override
-                            public void accept(int result) {
-                                plogd("handleEventWaitForSatelliteEnablingResponseTimedOut: "
-                                        + "disable satellite result=" + result);
-                            }
-                        };
-                        Consumer<Integer> result =
-                                FunctionalUtils.ignoreRemoteException(callback::accept);
-                        mSatelliteDisabledRequest = new RequestSatelliteEnabledArgument(
-                                false, false, false, result);
-                        sendRequestAsync(CMD_SET_SATELLITE_ENABLED, mSatelliteDisabledRequest,
-                                null);
+        if (argument.enableSatellite) {
+            resetSatelliteEnabledRequest();
+            abortSatelliteEnableAttributesUpdateRequest(SATELLITE_RESULT_REQUEST_ABORTED);
+            if (getSatelliteDisabledRequest() == null) {
+                IIntegerConsumer callback = new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        plogd("handleEventWaitForSatelliteEnablingResponseTimedOut: "
+                                + "disable satellite result=" + result);
                     }
+                };
+                Consumer<Integer> result =
+                        FunctionalUtils.ignoreRemoteException(callback::accept);
+
+                RequestSatelliteEnabledArgument request;
+                synchronized (mSatelliteEnabledRequestLock) {
+                    mSatelliteDisabledRequest = new RequestSatelliteEnabledArgument(
+                            false, false, false, result);
+                    request = mSatelliteDisabledRequest;
                 }
 
-                mControllerMetricsStats.reportServiceEnablementFailCount();
-                mSessionMetricsStats.setInitializationResult(SATELLITE_RESULT_MODEM_TIMEOUT)
-                        .setSatelliteTechnology(getSupportedNtnRadioTechnology())
-                        .setInitializationProcessingTime(
-                                System.currentTimeMillis() - mSessionProcessingTimeStamp)
-                        .setIsDemoMode(mIsDemoModeEnabled)
-                        .setCarrierId(getSatelliteCarrierId())
-                        .reportSessionMetrics();
-            } else {
-                resetSatelliteDisabledRequest();
-                mControllerMetricsStats.onSatelliteDisabled();
-                mSessionMetricsStats.setTerminationResult(SATELLITE_RESULT_MODEM_TIMEOUT)
-                        .setSatelliteTechnology(getSupportedNtnRadioTechnology())
-                        .setTerminationProcessingTime(
-                                System.currentTimeMillis() - mSessionProcessingTimeStamp)
-                        .setSessionDurationSec(calculateSessionDurationTimeSec())
-                        .reportSessionMetrics();
+                sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, null);
             }
-            notifyEnablementFailedToSatelliteSessionController(argument.enableSatellite);
-            mSessionStartTimeStamp = 0;
-            mSessionProcessingTimeStamp = 0;
+
+            mControllerMetricsStats.reportServiceEnablementFailCount();
+            mSessionMetricsStats.setInitializationResult(SATELLITE_RESULT_MODEM_TIMEOUT)
+                    .setSatelliteTechnology(getSupportedNtnRadioTechnology())
+                    .setInitializationProcessingTime(
+                            System.currentTimeMillis() - mSessionProcessingTimeStamp)
+                    .setIsDemoMode(mIsDemoModeEnabled)
+                    .setCarrierId(getSatelliteCarrierId())
+                    .reportSessionMetrics();
+        } else {
+            resetSatelliteDisabledRequest();
+            mControllerMetricsStats.onSatelliteDisabled();
+            mSessionMetricsStats.setTerminationResult(SATELLITE_RESULT_MODEM_TIMEOUT)
+                    .setSatelliteTechnology(getSupportedNtnRadioTechnology())
+                    .setTerminationProcessingTime(
+                            System.currentTimeMillis() - mSessionProcessingTimeStamp)
+                    .setSessionDurationSec(calculateSessionDurationTimeSec())
+                    .reportSessionMetrics();
         }
+        notifyEnablementFailedToSatelliteSessionController(argument.enableSatellite);
+        mSessionStartTimeStamp = 0;
+        mSessionProcessingTimeStamp = 0;
     }
 
     private void handleCmdUpdateNtnSignalStrengthReporting(boolean shouldReport) {
@@ -5603,7 +6508,7 @@
             return;
         }
 
-        if (!isSatelliteEnabled()) {
+        if (!isSatelliteEnabledOrBeingEnabled()) {
             plogd("handleCmdUpdateNtnSignalStrengthReporting: ignore request, satellite is "
                     + "disabled");
             return;
@@ -5680,13 +6585,14 @@
                     + mIsNotificationShowing);
         }
         if (isNtn.first) {
-            if (!notificationKeyStatus) {
+            if (!notificationKeyStatus && getCarrierRoamingNtnConnectType(isNtn.second)
+                    == CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC) {
                 updateSatelliteSystemNotification(isNtn.second,
                         CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC,
                         /*visible*/ true);
             }
         } else if (mIsNotificationShowing
-                && !isSatelliteConnectedViaCarrierWithinHysteresisTime()) {
+                && !isSatelliteConnectedViaCarrierWithinHysteresisTime().first) {
             // Dismiss the notification if it is still displaying.
             dismissSatelliteNotification();
         }
@@ -5697,6 +6603,23 @@
         updateSatelliteSystemNotification(-1, -1,/*visible*/ false);
     }
 
+    public boolean isSatelliteSystemNotificationsEnabled(int carrierRoamingNtnConnectType) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            return false;
+        }
+        if (carrierRoamingNtnConnectType
+            != CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_MANUAL) {
+            return true;
+        }
+        boolean notifySatelliteAvailabilityEnabled =
+            mContext.getResources().getBoolean(R.bool.config_satellite_should_notify_availability);
+        Boolean isSatelliteSupported = getIsSatelliteSupported();
+        if(isSatelliteSupported == null) {
+            return false;
+        }
+        return notifySatelliteAvailabilityEnabled && isSatelliteSupported;
+    }
+
     /**
      * Update the system notification to reflect the current satellite status, that's either already
      * connected OR needs to be manually enabled. The device should only display one notification
@@ -5710,6 +6633,11 @@
      */
     private void updateSatelliteSystemNotification(int subId,
             @CARRIER_ROAMING_NTN_CONNECT_TYPE int carrierRoamingNtnConnectType, boolean visible) {
+        if (!isSatelliteSystemNotificationsEnabled(carrierRoamingNtnConnectType)) {
+            plogd("updateSatelliteSystemNotification: satellite notifications are not enabled.");
+            return;
+        }
+
         plogd("updateSatelliteSystemNotification subId=" + subId + ", carrierRoamingNtnConnectType="
                 + SatelliteServiceUtils.carrierRoamingNtnConnectTypeToString(
                 carrierRoamingNtnConnectType) + ", visible=" + visible);
@@ -6015,7 +6943,7 @@
                         AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
 
         for (NetworkRegistrationInfo nri : nriList) {
-            if (nri.isInService() || nri.isEmergencyEnabled()) {
+            if (nri.isInService()) {
                 logv("getWwanIsInService: return true");
                 return true;
             }
@@ -6075,6 +7003,13 @@
         }
     }
 
+    private void plogv(@NonNull String log) {
+        Rlog.v(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.debug(TAG, log);
+        }
+    }
+
     private void handlePersistentLoggingOnSessionStart(RequestSatelliteEnabledArgument argument) {
         if (mPersistentLogger == null) {
             return;
@@ -6135,6 +7070,10 @@
         return TimeUnit.SECONDS.toMillis(duration);
     }
 
+    private long getEvaluateEsosProfilesPrioritizationDurationMillis() {
+        return TimeUnit.MINUTES.toMillis(1);
+    }
+
     /**
      * Calculate priority
      * 1. Active eSOS profiles are higher priority than inactive eSOS profiles.
@@ -6148,11 +7087,19 @@
             plogd("evaluateESOSProfilesPrioritization: Flag CarrierRoamingNbIotNtn is disabled");
             return;
         }
+
+        if (isSatelliteEnabledOrBeingEnabled()) {
+            plogd("evaluateESOSProfilesPrioritization: Skip evaluation as satellite is enabled "
+                    + "or being enabled");
+            return;
+        }
+
         boolean isChanged = false;
         List<SubscriptionInfo> allSubInfos = mSubscriptionManagerService.getAllSubInfoList(
                 mContext.getOpPackageName(), mContext.getAttributionTag());
         // Key : priority - lower value has higher priority; Value : List<SubscriptionInfo>
-        Map<Integer, List<SubscriptionInfo>> newSubsInfoListPerPriority = new HashMap<>();
+        TreeMap<Integer, List<SubscriptionInfo>> newSubsInfoListPerPriority = new TreeMap<>();
+        plogd("evaluateESOSProfilesPrioritization: allSubInfos.size()=" + allSubInfos.size());
         synchronized (mSatelliteTokenProvisionedLock) {
             for (SubscriptionInfo info : allSubInfos) {
                 int subId = info.getSubscriptionId();
@@ -6161,13 +7108,27 @@
                         mSubscriptionManagerService.getDefaultSmsSubId() == subId;
                 boolean isNtnOnly = info.isOnlyNonTerrestrialNetwork();
                 boolean isESOSSupported = info.isSatelliteESOSSupported();
+                boolean isCarrierSatelliteHigherPriority =
+                    isCarrierSatelliteHigherPriority(info);
                 if (!isNtnOnly && !isESOSSupported) {
                     continue;
                 }
+                if (!isActive && !isNtnOnly) {
+                    continue;
+                }
+                if (!isNtnOnly && !isCarrierConfigLoaded(subId)) {
+                    // Skip to add priority list if the carrier config is not loaded properly
+                    // for the given carrier subscription.
+                    continue;
+                }
 
-                int keyPriority = (isESOSSupported && isActive && isDefaultSmsSubId) ? 0
-                        : (isESOSSupported && isActive) ? 1
-                                : (isNtnOnly) ? 2 : (isESOSSupported) ? 3 : -1;
+                int keyPriority = (isESOSSupported && isActive && isDefaultSmsSubId
+                    && isCarrierSatelliteHigherPriority)
+                    ? 0 : (isESOSSupported && isActive &&
+                        isCarrierSatelliteHigherPriority)
+                        ? 1 : (isNtnOnly)
+                            ? 2 : (isESOSSupported)
+                                ? 3 : -1;
                 if (keyPriority != -1) {
                     newSubsInfoListPerPriority.computeIfAbsent(keyPriority,
                             k -> new ArrayList<>()).add(info);
@@ -6189,8 +7150,13 @@
                     logd("Old phone number is removed: id = " + subId);
                     isChanged = true;
                 }
+                if (!newSubscriberId.isEmpty()) {
+                    mSubscriberIdPerSub.put(newSubscriberId, subId);
+                }
             }
         }
+        plogd("evaluateESOSProfilesPrioritization: newSubsInfoListPerPriority.size()="
+                  + newSubsInfoListPerPriority.size());
 
         if (!mHasSentBroadcast && newSubsInfoListPerPriority.size() == 0) {
             logd("evaluateESOSProfilesPrioritization: no satellite subscription available");
@@ -6199,27 +7165,45 @@
 
         // If priority has changed, send broadcast for provisioned ESOS subs IDs
         synchronized (mSatelliteTokenProvisionedLock) {
+            List<SatelliteSubscriberProvisionStatus> newEvaluatedSubscriberProvisionStatus =
+                    getPrioritizedSatelliteSubscriberProvisionStatusList(
+                            newSubsInfoListPerPriority);
             if (isPriorityChanged(mSubsInfoListPerPriority, newSubsInfoListPerPriority)
+                    || isSubscriberContentChanged(mLastEvaluatedSubscriberProvisionStatus,
+                            newEvaluatedSubscriberProvisionStatus)
                     || isChanged) {
                 mSubsInfoListPerPriority = newSubsInfoListPerPriority;
+                mLastEvaluatedSubscriberProvisionStatus = newEvaluatedSubscriberProvisionStatus;
                 sendBroadCastForProvisionedESOSSubs();
                 mHasSentBroadcast = true;
+                selectBindingSatelliteSubscription(false);
             }
         }
     }
 
+    // to check if the contents of carrier config is loaded properly
+    private Boolean isCarrierConfigLoaded(int subId) {
+        PersistableBundle carrierConfig = mCarrierConfigManager
+                .getConfigForSubId(subId, KEY_CARRIER_CONFIG_APPLIED_BOOL);
+        return carrierConfig != null ? carrierConfig.getBoolean(
+                CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL) : false;
+    }
+
     // The subscriberId for ntnOnly SIMs is the Iccid, whereas for ESOS supported SIMs, the
     // subscriberId is the Imsi prefix 6 digit + phone number.
-    private Pair<String, Integer> getSubscriberIdAndType(SubscriptionInfo info) {
+    private Pair<String, Integer> getSubscriberIdAndType(@Nullable SubscriptionInfo info) {
         String subscriberId = "";
         @SatelliteSubscriberInfo.SubscriberIdType int subscriberIdType =
                 SatelliteSubscriberInfo.ICCID;
-        if (info.isSatelliteESOSSupported()) {
-            subscriberId = getPhoneNumberBasedCarrier(info.getSubscriptionId());
-            subscriberIdType = SatelliteSubscriberInfo.IMSI_MSISDN;
+        if (info == null) {
+            logd("getSubscriberIdAndType: subscription info is null");
+            return new Pair<>(subscriberId, subscriberIdType);
         }
         if (info.isOnlyNonTerrestrialNetwork()) {
             subscriberId = info.getIccId();
+        } else if (info.isSatelliteESOSSupported()) {
+            subscriberId = getPhoneNumberBasedCarrier(info.getSubscriptionId());
+            subscriberIdType = SatelliteSubscriberInfo.IMSI_MSISDN;
         }
         logd("getSubscriberIdAndType: subscriberId=" + subscriberId + ", subscriberIdType="
                 + subscriberIdType);
@@ -6267,6 +7251,24 @@
         return false;
     }
 
+    // Checks if there are any changes between subscriberInfos. return false if the same.
+    // Note that, Use lists with the same priority so we can compare contents properly.
+    private boolean isSubscriberContentChanged(List<SatelliteSubscriberProvisionStatus> currentList,
+            List<SatelliteSubscriberProvisionStatus> newList) {
+        if (currentList.size() != newList.size()) {
+            return true;
+        }
+        for (int i = 0; i < currentList.size(); i++) {
+            SatelliteSubscriberProvisionStatus curSub = currentList.get(i);
+            SatelliteSubscriberProvisionStatus newSub = newList.get(i);
+            if (!curSub.getSatelliteSubscriberInfo().equals(newSub.getSatelliteSubscriberInfo())) {
+                logd("isSubscriberContentChanged: cur=" + curSub + " , new=" + newSub);
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void sendBroadCastForProvisionedESOSSubs() {
         String packageName = getConfigSatelliteGatewayServicePackage();
         String className = getConfigSatelliteCarrierRoamingEsosProvisionedClass();
@@ -6300,6 +7302,29 @@
     }
 
     /**
+     * Request to get the name to display for Satellite.
+     *
+     * @param result The result receiver that returns the name to display for the satellite
+     *               or an error code if the request failed.
+     */
+    public void requestSatelliteDisplayName(@NonNull ResultReceiver result) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("requestSatelliteDisplayName: carrierRoamingNbIotNtn flag is disabled");
+            result.send(SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED, null);
+            return;
+        }
+
+        int subId = getSelectedSatelliteSubId();
+        String displayName = getConfigForSubId(subId).getString(
+                KEY_SATELLITE_DISPLAY_NAME_STRING, "Satellite");
+
+        plogd("requestSatelliteDisplayName: " + displayName);
+        Bundle bundle = new Bundle();
+        bundle.putString(SatelliteManager.KEY_SATELLITE_DISPLAY_NAME, displayName);
+        result.send(SATELLITE_RESULT_SUCCESS, bundle);
+    }
+
+    /**
      * Request to get list of prioritized satellite tokens to be used for provision.
      *
      * @param result The result receiver, which returns the list of prioritized satellite tokens
@@ -6320,10 +7345,18 @@
 
     private List<SatelliteSubscriberProvisionStatus>
             getPrioritizedSatelliteSubscriberProvisionStatusList() {
+        synchronized (mSatelliteTokenProvisionedLock) {
+            return getPrioritizedSatelliteSubscriberProvisionStatusList(mSubsInfoListPerPriority);
+        }
+    }
+
+    private List<SatelliteSubscriberProvisionStatus>
+            getPrioritizedSatelliteSubscriberProvisionStatusList(
+                    Map<Integer, List<SubscriptionInfo>> subsInfoListPerPriority) {
         List<SatelliteSubscriberProvisionStatus> list = new ArrayList<>();
         synchronized (mSatelliteTokenProvisionedLock) {
-            for (int priority : mSubsInfoListPerPriority.keySet()) {
-                List<SubscriptionInfo> infoList = mSubsInfoListPerPriority.get(priority);
+            for (int priority : subsInfoListPerPriority.keySet()) {
+                List<SubscriptionInfo> infoList = subsInfoListPerPriority.get(priority);
                 if (infoList == null) {
                     logd("getPrioritySatelliteSubscriberProvisionStatusList: no exist this "
                             + "priority " + priority);
@@ -6355,7 +7388,7 @@
                             + ", provisioned=" + provisioned);
                     list.add(new SatelliteSubscriberProvisionStatus.Builder()
                             .setSatelliteSubscriberInfo(satelliteSubscriberInfo)
-                            .setProvisionStatus(provisioned).build());
+                            .setProvisioned(provisioned).build());
                     mSubscriberIdPerSub.put(subscriberId, info.getSubscriptionId());
                 }
             }
@@ -6363,6 +7396,168 @@
         return list;
     }
 
+    public int getSelectedSatelliteSubId() {
+        synchronized (mSatelliteTokenProvisionedLock) {
+            plogd("getSelectedSatelliteSubId: subId=" + mSelectedSatelliteSubId);
+            return mSelectedSatelliteSubId;
+        }
+    }
+
+    /**
+     * Request to get the currently selected satellite subscription id.
+     *
+     * @param result The result receiver that returns the currently selected satellite subscription
+     *               id if the request is successful or an error code if the request failed.
+     */
+    public void requestSelectedNbIotSatelliteSubscriptionId(@NonNull ResultReceiver result) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            result.send(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, null);
+            logd("requestSelectedNbIotSatelliteSubscriptionId: carrierRoamingNbIotNtn is disabled");
+            return;
+        }
+
+        int selectedSatelliteSubId = getSelectedSatelliteSubId();
+        plogd("requestSelectedNbIotSatelliteSubscriptionId: " + selectedSatelliteSubId);
+        if (selectedSatelliteSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            result.send(SATELLITE_RESULT_NO_VALID_SATELLITE_SUBSCRIPTION, null);
+            logd("requestSelectedNbIotSatelliteSubscriptionId: "
+                    + "selectedSatelliteSubId is invalid");
+            return;
+        }
+
+        Bundle bundle = new Bundle();
+        bundle.putInt(SatelliteManager.KEY_SELECTED_NB_IOT_SATELLITE_SUBSCRIPTION_ID,
+                selectedSatelliteSubId);
+        result.send(SATELLITE_RESULT_SUCCESS, bundle);
+    }
+
+    private void selectBindingSatelliteSubscription(boolean shouldIgnoreEnabledState) {
+        if ((isSatelliteEnabled() || isSatelliteBeingEnabled()) && !shouldIgnoreEnabledState) {
+            plogd("selectBindingSatelliteSubscription: satellite subscription will be selected "
+                    + "once the satellite session ends");
+            return;
+        }
+
+        int selectedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        List<SatelliteSubscriberProvisionStatus> satelliteSubscribers =
+                getPrioritizedSatelliteSubscriberProvisionStatusList();
+
+        for (SatelliteSubscriberProvisionStatus status : satelliteSubscribers) {
+            int subId = getSubIdFromSubscriberId(
+                    status.getSatelliteSubscriberInfo().getSubscriberId());
+
+            if (status.isProvisioned() && isActiveSubId(subId) &&
+                isSatelliteAvailableAtCurrentLocation(
+                    mSubscriptionManagerService.getSubscriptionInfo(subId))) {
+                selectedSubId = subId;
+                break;
+            }
+        }
+
+        if (selectedSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                && isSatelliteSupportedViaOem()) {
+            selectedSubId = getNtnOnlySubscriptionId();
+        }
+
+        int preSelectedSatelliteSubId = getSelectedSatelliteSubId();
+        setSelectedSatelliteSubId(selectedSubId);
+        if (preSelectedSatelliteSubId != getSelectedSatelliteSubId()) {
+            plogd("selectBindingSatelliteSubscription: SelectedSatelliteSubId changed");
+            evaluateCarrierRoamingNtnEligibilityChange();
+        }
+
+        setSatellitePhone(selectedSubId);
+        if (selectedSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            int carrierId = getSatelliteCarrierId();
+            if (carrierId != UNKNOWN_CARRIER_ID) {
+                mControllerMetricsStats.setCarrierId(carrierId);
+            } else {
+                logd("selectBindingSatelliteSubscription: Carrier ID is UNKNOWN_CARRIER_ID");
+            }
+            mControllerMetricsStats.setIsNtnOnlyCarrier(isNtnOnlyCarrier());
+        }
+        plogd("selectBindingSatelliteSubscription: SelectedSatelliteSubId=" + selectedSubId);
+        handleEventSelectedNbIotSatelliteSubscriptionChanged(selectedSubId);
+        handleCarrierRoamingNtnAvailableServicesChanged();
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected boolean isCarrierSatelliteHigherPriority(SubscriptionInfo info) {
+        if(!isSatelliteAccessAllowedAtCurrentLocation()) {
+            return true;
+        }
+        if(isSatelliteAvailableAtCurrentLocation(info)) {
+            return true;
+        }
+        return false;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected boolean isSatelliteAvailableAtCurrentLocation(@Nullable SubscriptionInfo info) {
+        if(info == null) {
+            plogd("isSatelliteAvailableAtCurrentLocation: subscriptionInfo is null");
+            return false;
+        }
+        if (!isSatelliteAccessAllowedAtCurrentLocation()) {
+            plogd("isSatelliteAvailableAtCurrentLocation: satellite access is not allowed at " +
+                    "current location");
+            return false;
+        }
+        if(info.isOnlyNonTerrestrialNetwork()) {
+            return true;
+        }
+
+        int[] carrierTagIdsArray = mContext.getResources().getIntArray(
+            R.array.config_verizon_satellite_enabled_tagids);
+        List<Integer> carrierTagIds = null;
+
+        if(carrierTagIdsArray != null && carrierTagIdsArray.length > 0) {
+            carrierTagIds = Arrays.stream(carrierTagIdsArray)
+                .boxed()
+                .collect(Collectors.toList());
+        }
+
+        if(carrierTagIds == null) {
+            plogd("isSatelliteAvailableAtCurrentLocation: tagids for carrier satellite enabled " +
+                    "are not available");
+            return false;
+        }
+
+        return isCarrierSatelliteAvailableAtCurrentLocation(carrierTagIds);
+    }
+
+    /**
+     * Compares tagIds and determine if
+     * carrier satellite is available at current location while selecting highest priority profile.
+     *
+     * @param carrierTagIds a list of integer tagIds representing regions where carrier satellite
+     * coverage is available.
+     * @return {@code true} if the carrier satellite is available at current location,
+     *      {@code false} otherwise.
+     */
+    public boolean isCarrierSatelliteAvailableAtCurrentLocation(
+        List<Integer> carrierTagIds) {
+        synchronized (mSatelliteAccessConfigLock) {
+            return !Collections.disjoint(carrierTagIds, mCurrentLocationTagIds);
+        }
+    }
+
+    private int getSubIdFromSubscriberId(String subscriberId) {
+        synchronized (mSatelliteTokenProvisionedLock) {
+            return mSubscriberIdPerSub.getOrDefault(subscriberId,
+                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        }
+    }
+
+    private boolean isActiveSubId(int subId) {
+        SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId);
+        if (subInfo == null) {
+            logd("isActiveSubId: subscription associated with subId=" + subId + " not found");
+            return false;
+        }
+        return subInfo.isActive();
+    }
+
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected boolean isSubscriptionProvisioned(int subId) {
         plogd("isSubscriptionProvisioned: subId=" + subId);
@@ -6393,47 +7588,188 @@
             @NonNull ResultReceiver result) {
         if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
             result.send(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, null);
+            logd("provisionSatellite: carrierRoamingNbIotNtn not support");
             return;
         }
-        if (list.size() == 0) {
+        if (list.isEmpty()) {
             result.send(SATELLITE_RESULT_INVALID_ARGUMENTS, null);
+            logd("provisionSatellite: SatelliteSubscriberInfo list is empty");
             return;
         }
 
         logd("provisionSatellite:" + list);
         RequestProvisionSatelliteArgument request = new RequestProvisionSatelliteArgument(list,
-                result);
+                result, true);
         sendRequestAsync(CMD_UPDATE_PROVISION_SATELLITE_TOKEN, request, null);
+        incrementResultReceiverCount("SC:provisionSatellite");
     }
 
+    /**
+     * Request to update system selection channels.
+     *
+     * @param result The result receiver that returns if the request is successful or
+     *               an error code if the request failed.
+     */
+    public void updateSystemSelectionChannels(
+            @NonNull List<SystemSelectionSpecifier> selectionSpecifiers,
+            @NonNull ResultReceiver result) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("updateSystemSelectionChannels: "
+                    + "carrierRoamingNbIotNtn flag is disabled");
+            result.send(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, null);
+            return;
+        }
+
+        sendRequestAsync(CMD_UPDATE_SYSTEM_SELECTION_CHANNELS,
+                new UpdateSystemSelectionChannelsArgument(selectionSpecifiers, result), null);
+    }
+
+    /**
+     * @param subId Subscription ID.
+     * @return The The map of earfcns with key: regional satellite config Id,
+     * value: set of earfcns in the corresponding regions associated with the {@code subId}.
+     */
+    @NonNull
+    public Map<String, Set<Integer>> getRegionalSatelliteEarfcns(int subId) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            logd("getRegionalSatelliteEarfcns: carrierRoamingNbIotNtnFlag is disabled");
+            return new HashMap<>();
+        }
+        synchronized (mRegionalSatelliteEarfcnsLock) {
+            if (mRegionalSatelliteEarfcns.containsKey(subId)) {
+                return mRegionalSatelliteEarfcns.get(subId);
+            } else {
+                logd("getRegionalSatelliteEarfcns: Earfcns for subId: " + subId + " not found");
+                return new HashMap<>();
+            }
+        }
+    }
+
+    /**
+     * Update regional satellite earfcn information from carrier config.
+     */
+    public void updateRegionalSatelliteEarfcns(int subId) {
+        plogd("updateRegionalSatelliteEarfcns with subId " + subId);
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("updateRegionalSatelliteEarfcns: "
+                    + "carrierRoamingNbIotNtn flag is disabled");
+            return;
+        }
+
+        synchronized (mRegionalSatelliteEarfcnsLock) {
+            mRegionalSatelliteEarfcns.put(subId,
+                    readRegionalSatelliteEarfcnsFromCarrierConfig(subId));
+        }
+    }
+
+    /**
+     * Deliver the list of deprovisioned satellite subscriber ids.
+     *
+     * @param list List of deprovisioned satellite subscriber ids.
+     * @param result The result receiver that returns whether deliver success or fail.
+     */
+    public void deprovisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
+            @NonNull ResultReceiver result) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            result.send(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, null);
+            logd("deprovisionSatellite: carrierRoamingNbIotNtn not support");
+            return;
+        }
+        if (list.isEmpty()) {
+            result.send(SATELLITE_RESULT_INVALID_ARGUMENTS, null);
+            logd("deprovisionSatellite: SatelliteSubscriberInfo list is empty");
+            return;
+        }
+
+        logd("deprovisionSatellite:" + list);
+        RequestProvisionSatelliteArgument request = new RequestProvisionSatelliteArgument(list,
+                result, false);
+        sendRequestAsync(CMD_UPDATE_PROVISION_SATELLITE_TOKEN, request, null);
+        incrementResultReceiverCount("SC:provisionSatellite");
+    }
+
+    /**
+     * Inform whether application supports NTN SMS in satellite mode.
+     *
+     * This method is used by default messaging application to inform framework whether it supports
+     * NTN SMS or not.
+     *
+     * @param ntnSmsSupported {@code true} If application supports NTN SMS, else {@code false}.
+     */
+    public void setNtnSmsSupportedByMessagesApp(boolean ntnSmsSupported) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            return;
+        }
+        persistNtnSmsSupportedByMessagesApp(ntnSmsSupported);
+        handleCarrierRoamingNtnAvailableServicesChanged();
+    }
+
+    private void persistNtnSmsSupportedByMessagesApp(boolean ntnSmsSupported) {
+        plogd("persistNtnSmsSupportedByMessagesApp: ntnSmsSupported=" + ntnSmsSupported);
+        if (!loadSatelliteSharedPreferences()) return;
+
+        if (mSharedPreferences == null) {
+            ploge("persistNtnSmsSupportedByMessagesApp: mSharedPreferences is null");
+        } else {
+            mSharedPreferences.edit().putBoolean(
+                    NTN_SMS_SUPPORTED_BY_MESSAGES_APP_KEY, ntnSmsSupported).apply();
+            synchronized (mNtnSmsSupportedByMessagesAppLock) {
+                mNtnSmsSupportedByMessagesApp = ntnSmsSupported;
+            }
+        }
+    }
+
+    private boolean isNtnSmsSupportedByMessagesApp() {
+        synchronized (mNtnSmsSupportedByMessagesAppLock) {
+            if (mNtnSmsSupportedByMessagesApp != null) {
+                plogd("isNtnSmsSupportedByMessagesApp:" + mNtnSmsSupportedByMessagesApp);
+                return mNtnSmsSupportedByMessagesApp;
+            }
+        }
+
+        if (!loadSatelliteSharedPreferences()) return false;
+
+        if (mSharedPreferences == null) {
+            ploge("isNtnSmsSupportedByMessagesApp: mSharedPreferences is null");
+            return false;
+        } else {
+            boolean ntnSmsSupported = mSharedPreferences.getBoolean(
+                    NTN_SMS_SUPPORTED_BY_MESSAGES_APP_KEY, false);
+            synchronized (mNtnSmsSupportedByMessagesAppLock) {
+                mNtnSmsSupportedByMessagesApp = ntnSmsSupported;
+                plogd("isNtnSmsSupportedByMessagesApp:" + mNtnSmsSupportedByMessagesApp);
+            }
+            return ntnSmsSupported;
+        }
+    }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected void setSatellitePhone(int subId) {
         synchronized (mSatellitePhoneLock) {
             mSatellitePhone = SatelliteServiceUtils.getPhone(subId);
-            if (mSatellitePhone == null) {
-                mSatellitePhone = SatelliteServiceUtils.getPhone();
-            }
-            plogd("mSatellitePhone:" + (mSatellitePhone != null) + ", subId=" + subId);
-            int carrierId = mSatellitePhone.getCarrierId();
-            if (carrierId != UNKNOWN_CARRIER_ID) {
-                mControllerMetricsStats.setCarrierId(carrierId);
-            } else {
-                logd("setSatellitePhone: Carrier ID is UNKNOWN_CARRIER_ID");
-            }
+            plogd("mSatellitePhone: phoneId=" + (mSatellitePhone != null
+                      ? mSatellitePhone.getPhoneId() : "null") + ", subId=" + subId);
+        }
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void setSelectedSatelliteSubId(int subId) {
+        synchronized (mSatelliteTokenProvisionedLock) {
+            plogd("setSelectedSatelliteSubId: subId=" + subId);
+            mSelectedSatelliteSubId = subId;
         }
     }
 
     /** Return the carrier ID of the binding satellite subscription. */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public int getSatelliteCarrierId() {
-        synchronized (mSatellitePhoneLock) {
-            if (mSatellitePhone != null) {
-                return mSatellitePhone.getCarrierId();
-            } else {
+        synchronized (mSatelliteTokenProvisionedLock) {
+            SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(
+                    mSelectedSatelliteSubId);
+            if (subInfo == null) {
                 logd("getSatelliteCarrierId: returns UNKNOWN_CARRIER_ID");
                 return UNKNOWN_CARRIER_ID;
             }
+            return subInfo.getCarrierId();
         }
     }
 
@@ -6458,12 +7794,28 @@
             return false;
         }
 
+        if (!mIsRadioOn) {
+            plogd("isCarrierRoamingNtnEligible: radio is off");
+            return false;
+        }
+
+        boolean isSatelliteAccessAllowed = isSatelliteAccessAllowedAtCurrentLocation();
+        if (!isSatelliteAccessAllowed) {
+            plogd("isCarrierRoamingNtnEligible: satellite access is not allowed");
+            return false;
+        }
+
         if (phone == null) {
             plogd("isCarrierRoamingNtnEligible: phone is null");
             return false;
         }
 
-        int subId = phone.getSubId();
+        int subId = getSelectedSatelliteSubId();
+        if (!isSatelliteRoamingP2pSmSSupported(subId)) {
+            plogd("isCarrierRoamingNtnEligible(" + subId + "): doesn't support P2P SMS");
+            return false;
+        }
+
         if (!isSatelliteSupportedViaCarrier(subId)) {
             plogd("isCarrierRoamingNtnEligible[phoneId=" + phone.getPhoneId()
                     + "]: satellite is not supported via carrier");
@@ -6476,8 +7828,8 @@
             return false;
         }
 
-        if (!isSatelliteServiceSupportedByCarrier(subId,
-                NetworkRegistrationInfo.SERVICE_TYPE_SMS)) {
+        int[] services = getSupportedServicesOnCarrierRoamingNtn(subId);
+        if (!ArrayUtils.contains(services, NetworkRegistrationInfo.SERVICE_TYPE_SMS)) {
             plogd("isCarrierRoamingNtnEligible[phoneId=" + phone.getPhoneId()
                     + "]: SMS is not supported by carrier");
             return false;
@@ -6490,6 +7842,11 @@
             return false;
         }
 
+        if (mOverrideNtnEligibility != null) {
+            // TODO need to send the value from `mOverrideNtnEligibility` or simply true ?
+            return true;
+        }
+
         if (SatelliteServiceUtils.isCellularAvailable()) {
             plogd("isCarrierRoamingNtnEligible[phoneId=" + phone.getPhoneId()
                     + "]: cellular is available");
@@ -6507,11 +7864,19 @@
         return true;
     }
 
-    private boolean isSatelliteServiceSupportedByCarrier(int subId,
+
+    /**
+     * Checks if the satellite service is supported by the carrier for the specified
+     * subscription ID and servicetype.
+     *
+     * @param subId The subscription id.
+     * @param serviceType The type of service to check
+     */
+    public boolean isSatelliteServiceSupportedByCarrier(int subId,
             @NetworkRegistrationInfo.ServiceType int serviceType) {
         List<String> satellitePlmnList = getSatellitePlmnsForCarrier(subId);
         for (String satellitePlmn : satellitePlmnList) {
-            if (getSupportedSatelliteServices(subId, satellitePlmn).contains(serviceType)) {
+            if (getSupportedSatelliteServicesForPlmn(subId, satellitePlmn).contains(serviceType)) {
                 return true;
             }
         }
@@ -6526,15 +7891,6 @@
         }
     }
 
-    /**
-     * Return the highest priority satellite subscirption ID.
-     */
-    public int getHighestPrioritySubscrption() {
-        synchronized (mSatellitePhoneLock) {
-            return mSatellitePhone.getSubId();
-        }
-    }
-
     /** Start PointingUI if it is required. */
     public void startPointingUI() {
         synchronized (mNeedsSatellitePointingLock) {
@@ -6548,61 +7904,71 @@
         }
     }
 
-    private void requestIsSatelliteAllowedForCurrentLocation() {
-        plogd("requestIsSatelliteAllowedForCurrentLocation()");
-        synchronized (mSatellitePhoneLock) {
-            if (mCheckingAccessRestrictionInProgress) {
-                plogd("requestIsSatelliteCommunicationAllowedForCurrentLocation was already sent");
-                return;
-            }
-            mCheckingAccessRestrictionInProgress = true;
-        }
-
-        OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> callback =
-                new OutcomeReceiver<>() {
-                    @Override
-                    public void onResult(Boolean result) {
-                        plogd("requestIsSatelliteAllowedForCurrentLocation: result=" + result);
-                        sendMessage(obtainMessage(
-                                EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT, result));
-                    }
-
-                    @Override
-                    public void onError(SatelliteManager.SatelliteException ex) {
-                        plogd("requestIsSatelliteAllowedForCurrentLocation: onError, ex=" + ex);
-                        sendMessage(obtainMessage(
-                                EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT, false));
-                    }
-                };
-        requestIsSatelliteCommunicationAllowedForCurrentLocation(callback);
-    }
-
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    protected void requestIsSatelliteCommunicationAllowedForCurrentLocation(
-            @NonNull OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> callback) {
-        SatelliteManager satelliteManager = mContext.getSystemService(SatelliteManager.class);
-        if (satelliteManager == null) {
-            ploge("requestIsSatelliteCommunicationAllowedForCurrentLocation: "
-                    + "SatelliteManager is null");
+    protected void registerForSatelliteCommunicationAllowedStateChanged() {
+        if (mRegisteredForSatelliteCommunicationAllowedStateChanged.get()) {
+            if (DEBUG) {
+                plogd("registerForSatelliteCommunicationAllowedStateChanged: already registered.");
+            }
             return;
         }
 
-        satelliteManager.requestIsCommunicationAllowedForCurrentLocation(
-                this::post, callback);
+        SatelliteManager satelliteManager = mContext.getSystemService(SatelliteManager.class);
+        if (satelliteManager == null) {
+            ploge("registerForSatelliteCommunicationAllowedStateChanged: SatelliteManager is null");
+            return;
+        }
+
+        SatelliteCommunicationAllowedStateCallback allowedStateCallback =
+            new SatelliteCommunicationAllowedStateCallback() {
+                @Override
+                public void onSatelliteCommunicationAllowedStateChanged(boolean isAllowed) {
+                    plogd("onSatelliteCommunicationAllowedStateChanged: isAllowed="
+                        + isAllowed);
+                    synchronized (mSatelliteAccessConfigLock) {
+                        mSatelliteAccessAllowed = isAllowed;
+                    }
+                    evaluateESOSProfilesPrioritization();
+                    evaluateCarrierRoamingNtnEligibilityChange();
+                    handleCarrierRoamingNtnAvailableServicesChanged();
+                }
+
+                @Override
+                public void onSatelliteAccessConfigurationChanged(
+                    SatelliteAccessConfiguration satelliteAccessConfiguration) {
+                    plogd("onSatelliteAccessConfigurationChanged: satelliteAccessConfiguration="
+                        + satelliteAccessConfiguration);
+                    handleSatelliteAccessConfigUpdateResult(satelliteAccessConfiguration);
+                }
+            };
+        try {
+            satelliteManager.registerForCommunicationAllowedStateChanged(
+                    this::post, allowedStateCallback);
+        } catch(RuntimeException e) {
+            plogd("registerForSatelliteCommunicationAllowedStateChanged: " +
+                    "satelliteManager.registerForCommunicationAllowedStateChanged() failed, " +
+                    "e=" + e);
+            return;
+        }
+        mRegisteredForSatelliteCommunicationAllowedStateChanged.set(true);
     }
 
-    private void handleSatelliteAccessRestrictionCheckingResult(boolean satelliteAllowed) {
-        synchronized (mSatellitePhoneLock) {
-            mCheckingAccessRestrictionInProgress = false;
-            boolean eligible = isCarrierRoamingNtnEligible(mSatellitePhone);
-            plogd("handleSatelliteAccessRestrictionCheckingResult:"
-                    + " satelliteAllowed=" + satelliteAllowed
-                    + ", isCarrierRoamingNtnEligible=" + eligible
-                    + ", mNtnEligibilityHysteresisTimedOut=" + mNtnEligibilityHysteresisTimedOut);
-            if (satelliteAllowed && eligible && mNtnEligibilityHysteresisTimedOut) {
-                updateLastNotifiedNtnEligibilityAndNotify(true);
-                mNtnEligibilityHysteresisTimedOut = false;
+    private void handleSatelliteAccessConfigUpdateResult(
+        SatelliteAccessConfiguration satelliteAccessConfig) {
+        if(satelliteAccessConfig != null) {
+            synchronized (mSatelliteAccessConfigLock) {
+                plogd("handleSatelliteAccessConfigUpdateResult:" + " satelliteAccessConfig="
+                    + satelliteAccessConfig);
+                List<Integer> tagIds = satelliteAccessConfig.getTagIds();
+                if (!mCurrentLocationTagIds.equals(tagIds)) {
+                    mCurrentLocationTagIds = tagIds;
+                    sendMessageDelayed(obtainMessage(CMD_EVALUATE_ESOS_PROFILES_PRIORITIZATION),
+                        mEvaluateEsosProfilesPrioritizationDurationMillis);
+                }
             }
+        } else {
+                plogd("handleSatelliteAccessConfigUpdateResult: "
+                    + "satelliteAccessConfiguration is null");
         }
     }
 
@@ -6623,6 +7989,42 @@
         });
     }
 
+    private void handleEventTerrestrialNetworkAvailableChanged(boolean isAvailable) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("handleEventTerrestrialNetworkAvailableChanged: "
+                    + "carrierRoamingNbIotNtn flag is disabled");
+            return;
+        }
+
+        plogd("handleEventTerrestrialNetworkAvailableChanged: " + isAvailable);
+
+        List<ISatelliteModemStateCallback> deadCallersList = new ArrayList<>();
+        mTerrestrialNetworkAvailableChangedListeners.values().forEach(listener -> {
+            try {
+                listener.onTerrestrialNetworkAvailableChanged(isAvailable);
+            } catch (RemoteException e) {
+                logd("handleEventTerrestrialNetworkAvailableChanged RemoteException: " + e);
+                deadCallersList.add(listener);
+            }
+        });
+        deadCallersList.forEach(listener -> {
+            mTerrestrialNetworkAvailableChangedListeners.remove(listener.asBinder());
+        });
+
+        if (isAvailable && !mIsEmergency) {
+            requestSatelliteEnabled(
+                    false /* enableSatellite */, false /* enableDemoMode */,
+                    false /* isEmergency */,
+                    new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            plogd("handleEventTerrestrialNetworkAvailableChanged:"
+                                    + " requestSatelliteEnabled result=" + result);
+                        }
+                    });
+        }
+    }
+
     /**
      * This API can be used by only CTS to override the cached value for the device overlay config
      * value :
@@ -6672,7 +8074,8 @@
         return result;
     }
 
-    private String getConfigSatelliteGatewayServicePackage() {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected String getConfigSatelliteGatewayServicePackage() {
         if (!mChangeIntentComponent) {
             return getStringFromOverlayConfig(
                     R.string.config_satellite_gateway_service_package);
@@ -6702,6 +8105,11 @@
         mContext.registerReceiver(mDefaultSmsSubscriptionChangedBroadcastReceiver, intentFilter);
     }
 
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected List<DeviceState> getSupportedDeviceStates() {
+        return mContext.getSystemService(DeviceStateManager.class).getSupportedDeviceStates();
+    }
+
     FeatureFlags getFeatureFlags() {
         return mFeatureFlags;
     }
@@ -6719,4 +8127,527 @@
             return !mWaitingForSatelliteModemOff;
         }
     }
+
+    /**
+     * Method to override the Carrier roaming Non-terrestrial network eligibility check
+     *
+     * @param state         flag to enable or disable the Ntn eligibility check.
+     * @param resetRequired reset overriding the check with adb command.
+     */
+    public boolean overrideCarrierRoamingNtnEligibilityChanged(boolean state,
+            boolean resetRequired) {
+        Log.d(TAG, "overrideCarrierRoamingNtnEligibilityChanged state = " + state
+                + "  resetRequired = " + resetRequired);
+        if (resetRequired) {
+            mOverrideNtnEligibility = null;
+        } else {
+            if (mOverrideNtnEligibility == null) {
+                mOverrideNtnEligibility = new AtomicBoolean(state);
+            } else {
+                mOverrideNtnEligibility.set(state);
+            }
+            synchronized (mSatellitePhoneLock) {
+                if (this.mSatellitePhone != null) {
+                    updateLastNotifiedNtnEligibilityAndNotify(state);
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * This method check for the key KEY_SATELLITE_MAX_DATAGRAM_SIZE in carrier config. If
+     * available it fetches the value and override the same in SatelliteCapabilities. Otherwise it
+     * uses the value in the existed mSatelliteCapabilities.
+     */
+    private void overrideSatelliteCapabilitiesIfApplicable() {
+        int subId = getSelectedSatelliteSubId();
+        PersistableBundle config = getPersistableBundle(subId);
+        if (config.containsKey(KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE_BYTES_INT)) {
+            int datagramSize = config.getInt(KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE_BYTES_INT);
+            SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId);
+            if (!(subInfo == null || subInfo.isOnlyNonTerrestrialNetwork())) {
+                synchronized (mSatelliteCapabilitiesLock) {
+                    this.mSatelliteCapabilities.setMaxBytesPerOutgoingDatagram(datagramSize);
+                }
+            }
+        }
+    }
+
+    /**
+     * This method returns subscription id for supporting Ntn Only
+     */
+    public int getNtnOnlySubscriptionId() {
+        List<SubscriptionInfo> infoList = mSubscriptionManagerService.getAllSubInfoList(
+                        mContext.getOpPackageName(), mContext.getAttributionTag());
+        int subId = infoList.stream()
+                .filter(info -> info.isOnlyNonTerrestrialNetwork())
+                .mapToInt(SubscriptionInfo::getSubscriptionId)
+                .findFirst()
+                .orElse(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        logd("getNtnOnlySubscriptionId: subId=" + subId);
+        return subId;
+    }
+
+    @Nullable
+    private List<SatelliteSubscriberInfo> getNtnOnlySatelliteSubscriberInfoList(
+            Consumer<Integer> result) {
+        SatelliteSubscriberInfo satelliteSubscriberInfo = getNtnOnlySatelliteSubscriberInfo();
+        if (satelliteSubscriberInfo == null) {
+            result.accept(SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
+            return null;
+        }
+        List<SatelliteSubscriberInfo> satelliteSubscriberInfoList = new ArrayList<>();
+        satelliteSubscriberInfoList.add(satelliteSubscriberInfo);
+
+        return satelliteSubscriberInfoList;
+    }
+
+    @Nullable private SatelliteSubscriberInfo getNtnOnlySatelliteSubscriberInfo() {
+        int ntnOnlySubId = getNtnOnlySubscriptionId();
+        if (ntnOnlySubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            logw("getNtnOnlySatelliteSubscriberInfo: no ntn only subscription found");
+            return null;
+        }
+        SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(ntnOnlySubId);
+        if (subInfo == null) {
+            logw("getNtnOnlySatelliteSubscriberInfo: no subscription info found for subId="
+                    + ntnOnlySubId);
+            return null;
+        }
+        return getSatelliteSubscriberInfo(subInfo);
+    }
+
+    @Nullable private SatelliteSubscriberInfo getSatelliteSubscriberInfo(
+        @NonNull SubscriptionInfo subInfo) {
+        Pair<String, Integer> subscriberIdPair = getSubscriberIdAndType(subInfo);
+        String subscriberId = subscriberIdPair.first;
+        int carrierId = subInfo.getCarrierId();
+        String apn = getConfigForSubId(subInfo.getSubscriptionId())
+                .getString(KEY_SATELLITE_NIDD_APN_NAME_STRING, "");
+        logd("getSatelliteSubscriberInfo: subInfo: " + subInfo + ", subscriberId:"
+                + subscriberId + " , carrierId=" + carrierId + " , apn=" + apn);
+        if (subscriberId.isEmpty()) {
+            logw("getSatelliteSubscriberInfo: not a satellite subscription.");
+            return null;
+        }
+        return new SatelliteSubscriberInfo.Builder().setSubscriberId(subscriberId)
+                        .setCarrierId(carrierId).setNiddApn(apn)
+                        .setSubId(subInfo.getSubscriptionId())
+                        .setSubscriberIdType(subscriberIdPair.second)
+                        .build();
+    }
+
+    private void handleCarrierRoamingNtnAvailableServicesChanged() {
+        int[] activeSubIds = mSubscriptionManagerService.getActiveSubIdList(true);
+        if (activeSubIds == null) {
+            plogd("handleCarrierRoamingNtnAvailableServicesChanged: activeSubIds is null.");
+            return;
+        }
+
+        plogd("handleCarrierRoamingNtnAvailableServicesChanged: activeSubIds size="
+                + activeSubIds.length);
+        for (int subId: activeSubIds) {
+            handleCarrierRoamingNtnAvailableServicesChanged(subId);
+        }
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void handleCarrierRoamingNtnAvailableServicesChanged(int subId) {
+        plogd("handleCarrierRoamingNtnAvailableServicesChanged: subId=" + subId);
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("handleCarrierRoamingNtnAvailableServicesChanged: "
+                    + "carrierRoamingNbIotNtn flag is disabled");
+            return;
+        }
+        updateLastNotifiedNtnAvailableServicesAndNotify(subId);
+        evaluateCarrierRoamingNtnEligibilityChange();
+    }
+
+    private void updateLastNotifiedNtnAvailableServicesAndNotify(int subId) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("notifyNtnAvailableServices: carrierRoamingNbIotNtn flag is disabled");
+            return;
+        }
+        Phone phone = SatelliteServiceUtils.getPhone(subId);
+        if (phone == null) {
+            plogd("notifyNtnAvailableServices: phone is null.");
+            return;
+        }
+        plogd("updateLastNotifiedNtnAvailableServicesAndNotify: phoneId= " + phone.getPhoneId());
+        int[] services = getSupportedServicesOnCarrierRoamingNtn(subId);
+        phone.notifyCarrierRoamingNtnAvailableServicesChanged(services);
+    }
+
+    /** Return services that are supported on carrier roaming non-terrestrial network. */
+    public int[] getSupportedServicesOnCarrierRoamingNtn(int subId) {
+        if (isSatelliteSupportedViaCarrier(subId)) {
+            // TODO: b/377367448 Cleanup get supported satellite services to align with starlink.
+            int[] services = getSupportedSatelliteServicesForCarrier(subId);
+            if (isP2PSmsDisallowedOnCarrierRoamingNtn(subId)) {
+                services = Arrays.stream(services).filter(
+                        value -> value != NetworkRegistrationInfo.SERVICE_TYPE_SMS).toArray();
+            }
+            return services;
+        }
+        return new int[0];
+    }
+
+    /**
+     * Whether the P2P SMS over carrier roaming satellite is disallowed or not.
+     *
+     * @param subId Associated subscription ID
+     * return {@code true} when the phone does not support P2P SMS over carrier roaming satellite
+     *        {@code false} otherwise
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean isP2PSmsDisallowedOnCarrierRoamingNtn(int subId) {
+        int carrierRoamingNtnConnectType = getCarrierRoamingNtnConnectType(subId);
+        if (carrierRoamingNtnConnectType == CARRIER_ROAMING_NTN_CONNECT_MANUAL) {
+            // Manual Connected
+            plogd("isP2PSmsDisallowedOnCarrierRoamingNtn: manual connect");
+            if (!isNtnSmsSupportedByMessagesApp()
+                    || !isApplicationSupportsP2P(mSatelliteGatewayServicePackageName)) {
+                plogd("isP2PSmsDisallowedOnCarrierRoamingNtn: APKs do not supports P2P");
+                return true;
+            }
+        }
+        plogd("isP2PSmsDisallowedOnCarrierRoamingNtn: P2P is supported");
+        return false;
+    }
+
+    @NonNull
+    private int[] getSupportedSatelliteServicesForCarrier(int subId) {
+        PersistableBundle config = getPersistableBundle(subId);
+        int[] availableServices = config.getIntArray(
+                KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY);
+        if (availableServices == null) {
+            logd("getSupportedSatelliteServicesForCarrier: defaultCapabilities is null");
+            return new int[0];
+        }
+        logd("getSupportedSatelliteServicesForCarrier: subId=" + subId
+                + ", return default values " + Arrays.toString(availableServices));
+        return availableServices;
+    }
+
+    /**
+     * Whether application supports the P2P SMS to connect to carrier roaming non-terrestrial
+     * network.
+     *
+     * @param packageName application's default package name
+     * return {@code true} when the application supports P2P SMS over the roaming satellite
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean isApplicationSupportsP2P(String packageName) {
+        PackageManager pm = mContext.getPackageManager();
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        try {
+            applicationInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
+        } catch (PackageManager.NameNotFoundException e) {
+            logd("isApplicationSupportsP2P pkgName: " + packageName + " is not installed.");
+            return false;
+        }
+        if (applicationInfo == null || applicationInfo.metaData == null) {
+            logd("isApplicationSupportsP2P pkgName: " + packageName + " meta-data info is empty.");
+            return false;
+        }
+        return applicationInfo.metaData.getBoolean(
+                SatelliteManager.METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT);
+    }
+
+    /**
+     * Registers for the applications state changed.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public void registerApplicationStateChanged() {
+        mDefaultSmsPackageName = Telephony.Sms.getDefaultSmsPackage(mContext);
+        mSatelliteGatewayServicePackageName = getConfigSatelliteGatewayServicePackage();
+
+        IntentFilter packageFilter = new IntentFilter();
+        packageFilter.addAction(Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        packageFilter.addDataScheme("package");
+        mContext.registerReceiver(mPackageStateChangedReceiver, packageFilter,
+                mContext.RECEIVER_EXPORTED);
+    }
+
+
+    private void notifyEnabledStateChanged(boolean isEnabled) {
+        TelephonyRegistryManager trm = mContext.getSystemService(TelephonyRegistryManager.class);
+        if (trm == null) {
+            loge("Telephony registry service is down!");
+            return;
+        }
+
+        trm.notifySatelliteStateChanged(isEnabled);
+        logd("notifyEnabledStateChanged to " + isEnabled);
+    }
+
+    private NtnSignalStrength getCarrierRoamingNtnSignalStrength(@NonNull Phone phone) {
+        NtnSignalStrength carrierRoamingNtnSignalStrength = new NtnSignalStrength(
+                NTN_SIGNAL_STRENGTH_NONE);
+
+        if (isInCarrierRoamingNbIotNtn(phone)) {
+            if (isInConnectedState()) {
+                synchronized (mNtnSignalsStrengthLock) {
+                    carrierRoamingNtnSignalStrength = mNtnSignalStrength;
+                }
+                plogd("getCarrierRoamingNtnSignalStrength[phoneId=" + phone.getPhoneId()
+                        + "]: in carrier roaming nb iot ntn mode.");
+            }
+        } else if (isInSatelliteModeForCarrierRoaming(phone)) {
+            ServiceState serviceState = phone.getServiceState();
+            if (serviceState.getState() != ServiceState.STATE_OUT_OF_SERVICE) {
+                carrierRoamingNtnSignalStrength = new NtnSignalStrength(
+                        phone.getSignalStrength().getLevel());
+                plogd("getCarrierRoamingNtnSignalStrength[phoneId=" + phone.getPhoneId()
+                        + "]: is in satellite mode for carrier roaming.");
+            }
+        }
+
+        return carrierRoamingNtnSignalStrength;
+    }
+
+    /**
+     * Returns satellite connected state from modem, return true if connected.
+     */
+    public boolean isInConnectedState() {
+        synchronized (mSatelliteModemStateLock) {
+            switch (mSatelliteModemState) {
+                case SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED: //fallthrough
+                case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING: //fallthrough
+                case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING: //fallthrough
+                case SatelliteManager.SATELLITE_MODEM_STATE_IDLE:
+                    plogd("isInConnectedState: return true");
+                    return true;
+                default:
+                    plogd("isInConnectedState: return false");
+                    return false;
+            }
+        }
+    }
+
+    protected void updateLastNotifiedCarrierRoamingNtnSignalStrengthAndNotify(
+            @Nullable Phone phone) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) return;
+        if (phone == null) {
+            return;
+        }
+
+        NtnSignalStrength currSignalStrength = getCarrierRoamingNtnSignalStrength(phone);
+        int subId = phone.getSubId();
+        synchronized (mSatelliteConnectedLock) {
+            NtnSignalStrength lastNotifiedSignalStrength =
+                    mLastNotifiedCarrierRoamingNtnSignalStrength.get(subId);
+            if (lastNotifiedSignalStrength == null
+                    || lastNotifiedSignalStrength.getLevel() != currSignalStrength.getLevel()) {
+                mLastNotifiedCarrierRoamingNtnSignalStrength.put(subId, currSignalStrength);
+                phone.notifyCarrierRoamingNtnSignalStrengthChanged(currSignalStrength);
+            }
+        }
+    }
+
+    /** Returns whether to send SMS to DatagramDispatcher or not. */
+    public boolean shouldSendSmsToDatagramDispatcher(@Nullable Phone phone) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            if (!isInCarrierRoamingNbIotNtn(phone)) {
+                return false;
+            }
+
+            if (isDemoModeEnabled()) {
+                return false;
+            }
+
+            int[] services = getSupportedServicesOnCarrierRoamingNtn(phone.getSubId());
+            return ArrayUtils.contains(services, NetworkRegistrationInfo.SERVICE_TYPE_SMS);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /** Returns whether to drop SMS or not. */
+    public boolean shouldDropSms(@Nullable Phone phone) {
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            if (!isInCarrierRoamingNbIotNtn(phone)) {
+                return false;
+            }
+
+            int[] services = getSupportedServicesOnCarrierRoamingNtn(phone.getSubId());
+            return !ArrayUtils.contains(services, NetworkRegistrationInfo.SERVICE_TYPE_SMS);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private boolean isWaitingForSatelliteModemOff() {
+        synchronized (mSatelliteEnabledRequestLock) {
+            return mWaitingForSatelliteModemOff;
+        }
+    }
+
+    @Nullable
+    private Boolean getIsSatelliteSupported() {
+        synchronized (mIsSatelliteSupportedLock) {
+            return mIsSatelliteSupported;
+        }
+    }
+
+    private boolean isWaitingForDisableSatelliteModemResponse() {
+        synchronized (mSatelliteEnabledRequestLock) {
+            return mWaitingForDisableSatelliteModemResponse;
+        }
+    }
+
+    private boolean isSatelliteAccessAllowedAtCurrentLocation() {
+        synchronized (mSatelliteAccessConfigLock) {
+            return mSatelliteAccessAllowed;
+        }
+    }
+
+    @Nullable
+    private Boolean getIsSatelliteEnabled() {
+        synchronized (mIsSatelliteEnabledLock) {
+            return mIsSatelliteEnabled;
+        }
+    }
+
+    @Nullable
+    private RequestSatelliteEnabledArgument getSatelliteDisabledRequest() {
+        synchronized (mSatelliteEnabledRequestLock) {
+            return mSatelliteDisabledRequest;
+        }
+    }
+
+    private SatelliteCapabilities getSatelliteCapabilities() {
+        synchronized (mSatelliteCapabilitiesLock) {
+            return mSatelliteCapabilities;
+        }
+    }
+
+    private void setBTEnabledState(boolean enabled) {
+        synchronized (mRadioStateLock) {
+            mBTStateEnabled = enabled;
+        }
+    }
+
+    private boolean getBTEnabledState() {
+        synchronized (mRadioStateLock) {
+            return mBTStateEnabled;
+        }
+    }
+
+    private void setNfcEnabledState(boolean enabled) {
+        synchronized (mRadioStateLock) {
+            mNfcStateEnabled = enabled;
+        }
+    }
+
+    private boolean getNfcEnabledState() {
+        synchronized (mRadioStateLock) {
+            return mNfcStateEnabled;
+        }
+    }
+
+    private void setUwbEnabledState(boolean enabled) {
+        synchronized (mRadioStateLock) {
+            mUwbStateEnabled = enabled;
+        }
+    }
+
+    private boolean getUwbEnabledState() {
+        synchronized (mRadioStateLock) {
+            return mUwbStateEnabled;
+        }
+    }
+
+    private void setWifiEnabledState(boolean enabled) {
+        synchronized (mRadioStateLock) {
+            mWifiStateEnabled = enabled;
+        }
+    }
+
+    private boolean getWifiEnabledState() {
+        synchronized (mRadioStateLock) {
+            return mWifiStateEnabled;
+        }
+    }
+
+    /**
+     * Method to return the current data plan for the registered plmn based on entitlement
+     * provisioning information. Note: If no information at
+     * provisioning is supported this is overridden with operator carrier config information.
+     *
+     * @param subId current subscription id
+     * @param plmn current registered plmn information
+     *
+     * @return Data supported modes {@link SatelliteController#SATELLITE_DATA_PLAN_METERED}
+     */
+    public int getSatelliteDataPlanForPlmn(int subId, String plmn) {
+        if (plmn != null) {
+            synchronized (mSupportedSatelliteServicesLock) {
+                Map<String, Integer> dataplanMap = mEntitlementDataPlanMapPerCarrier.get(subId);
+                logd("data plan available for sub id:" + dataplanMap);
+                if (dataplanMap != null && dataplanMap.containsKey(plmn)) {
+                    return dataplanMap.get(plmn);
+                }
+            }
+        }
+        // TODO (Override with carrier config value when configuration defined)
+        return SATELLITE_DATA_PLAN_METERED;
+    }
+
+    /**
+     * Method to return the current satellite data service policy supported mode for the registered
+     * plmn based on entitlement provisioning information. Note: If no information at
+     * provisioning is supported this is overridden with operator carrier config information.
+     *
+     * @param subId current subscription id
+     * @param plmn current registered plmn information
+     *
+     * @return Supported modes {@link CarrierConfigManager.SATELLITE_DATA_SUPPORT_MODE}
+     */
+    public int getSatelliteDataServicePolicyForPlmn(int subId, String plmn) {
+        if (plmn != null) {
+            synchronized (mSupportedSatelliteServicesLock) {
+                Map<String, Integer> dataServicePolicy =
+                        mEntitlementDataServicePolicyMapPerCarrier.get(
+                        subId);
+                logd("data policy available for sub id:" + dataServicePolicy);
+                if (dataServicePolicy != null && dataServicePolicy.containsKey(plmn)) {
+                    return dataServicePolicy.get(plmn);
+                }
+            }
+        }
+        return getCarrierSatelliteDataSupportedMode(subId);
+    }
+
+    /**
+     * Method to return the current satellite voice service policy supported mode for the registered
+     * plmn based on entitlement provisioning information. Note: If no information at
+     * provisioning is supported this is overridden with operator carrier config information.
+     *
+     * @param subId current subscription id
+     * @param plmn current registered plmn information
+     *
+     * @return Supported modes {@link CarrierConfigManager.SATELLITE_DATA_SUPPORT_MODE}
+     */
+    public int getSatelliteVoiceServicePolicyForPlmn(int subId, String plmn) {
+        if (plmn != null) {
+            synchronized (mSupportedSatelliteServicesLock) {
+                Map<String, Integer> voiceServicePolicy =
+                        mEntitlementVoiceServicePolicyMapPerCarrier.get(
+                                subId);
+                logd("voice policy available for sub id:" + voiceServicePolicy);
+                if (voiceServicePolicy != null && voiceServicePolicy.containsKey(plmn)) {
+                    return voiceServicePolicy.get(plmn);
+                }
+            }
+        }
+        // TODO (Replace below code with related enum value, when voice service policy support mode
+        // is added)
+        return 0; // Restricted
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
index a19f802..5b032e6 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
@@ -41,6 +41,7 @@
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.SatelliteManager.SatelliteException;
 import android.telephony.satellite.SatelliteModemEnableRequestAttributes;
+import android.telephony.satellite.SystemSelectionSpecifier;
 import android.telephony.satellite.stub.INtnSignalStrengthConsumer;
 import android.telephony.satellite.stub.ISatellite;
 import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
@@ -104,6 +105,8 @@
             new RegistrantList();
     @NonNull private final RegistrantList mSatelliteRegistrationFailureRegistrants =
             new RegistrantList();
+    @NonNull private final RegistrantList mTerrestrialNetworkAvailableChangedRegistrants =
+            new RegistrantList();
 
     private class SatelliteListener extends ISatelliteListener.Stub {
 
@@ -192,6 +195,11 @@
             mSatelliteRegistrationFailureRegistrants.notifyResult(causeCode);
         }
 
+        @Override
+        public void onTerrestrialNetworkAvailableChanged(boolean isAvailable) {
+            mTerrestrialNetworkAvailableChangedRegistrants.notifyResult(isAvailable);
+        }
+
         private boolean notifyResultIfExpectedListener() {
             // Demo listener should notify results only during demo mode
             // Vendor listener should notify result only during real mode
@@ -587,6 +595,27 @@
     }
 
     /**
+     * Registers for the terrestrial network available changed.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForTerrestrialNetworkAvailableChanged(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+        mTerrestrialNetworkAvailableChangedRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregisters for the terrestrial network available changed.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForTerrestrialNetworkAvailableChanged(@NonNull Handler h) {
+        mTerrestrialNetworkAvailableChangedRegistrants.remove(h);
+    }
+
+    /**
      * Request to enable or disable the satellite service listening mode.
      * Listening mode allows the satellite service to listen for incoming pages.
      *
@@ -652,14 +681,14 @@
                 };
 
                 if (mSatelliteController.isDemoModeEnabled()) {
-                    mDemoSimulator.enableCellularModemWhileSatelliteModeIsOn(
+                    mDemoSimulator.enableTerrestrialNetworkScanWhileSatelliteModeIsOn(
                             enabled, errorCallback);
                 } else {
-                    mSatelliteService.enableCellularModemWhileSatelliteModeIsOn(
+                    mSatelliteService.enableTerrestrialNetworkScanWhileSatelliteModeIsOn(
                             enabled, errorCallback);
                 }
             } catch (RemoteException e) {
-                ploge("enableCellularModemWhileSatelliteModeIsOn: RemoteException " + e);
+                ploge("enableTerrestrialNetworkScanWhileSatelliteModeIsOn: RemoteException " + e);
                 if (message != null) {
                     sendMessageWithResult(
                             message, null, SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
@@ -1355,6 +1384,42 @@
         mExponentialBackoff.start();
     }
 
+    /**
+     * Request to update system selection channels
+     *
+     * @param systemSelectionSpecifiers system selection specifiers
+     * @param message The Message to send to result of the operation to.
+     */
+    public void updateSystemSelectionChannels(
+            @NonNull List<SystemSelectionSpecifier> systemSelectionSpecifiers,
+            @Nullable Message message) {
+        plogd("updateSystemSelectionChannels: SystemSelectionSpecifier: "
+                + systemSelectionSpecifiers.toString());
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.updateSystemSelectionChannels(SatelliteServiceUtils
+                                .toSystemSelectionSpecifier(systemSelectionSpecifiers),
+                        new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                int error = SatelliteServiceUtils.fromSatelliteError(result);
+                                plogd("updateSystemSelectionChannels: " + error);
+                                Binder.withCleanCallingIdentity(() ->
+                                        sendMessageWithResult(message, null, error));
+                            }
+                        });
+            } catch (RemoteException e) {
+                ploge("updateSystemSelectionChannels: RemoteException " + e);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
+            }
+        } else {
+            ploge("updateSystemSelectionChannels: Satellite service is unavailable.");
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
+        }
+    }
+
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected static void sendMessageWithResult(@NonNull Message message, @Nullable Object result,
             @SatelliteManager.SatelliteResult int error) {
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteNetworkInfo.java b/src/java/com/android/internal/telephony/satellite/SatelliteNetworkInfo.java
index 7db9195..f101f18 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteNetworkInfo.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteNetworkInfo.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.telephony.satellite;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Data class of the satellite configuration received from the entitlement server.
  */
@@ -28,9 +31,21 @@
      * 2. "metered"
      * 3. empty string. */
     public String mDataPlanType;
+    /** Stored the Allowed Services Info. with key as service type and value as service
+     *  policy for the plmn
+     *  Possible Service Type values: "data" and "voice".
+     *  Possible Service Policy values: "constrained" and "unconstrained".
+     */
+    public Map<String,String> mAllowedServicesInfo;
 
-    public SatelliteNetworkInfo(String plmn, String dataPlanType) {
+    public SatelliteNetworkInfo(String plmn, String dataPlanType,
+            Map<String,String> allowedServicesInfo) {
         mPlmn = plmn;
         mDataPlanType = dataPlanType;
+        if (allowedServicesInfo != null) {
+            mAllowedServicesInfo = new HashMap<>(allowedServicesInfo);
+        } else {
+            mAllowedServicesInfo = new HashMap<>();
+        }
     }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
index cca973f..fc79c49 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
@@ -24,6 +24,9 @@
 import static android.telephony.TelephonyManager.EXTRA_EMERGENCY_CALL_TO_SATELLITE_LAUNCH_INTENT;
 import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
 import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP;
 
 import static com.android.internal.telephony.flags.Flags.satellitePersistentLogging;
 import static com.android.internal.telephony.satellite.SatelliteController.INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE;
@@ -49,6 +52,7 @@
 import android.telephony.PersistentLogger;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsRegistrationAttributes;
@@ -73,8 +77,10 @@
 import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.metrics.SatelliteStats;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 
 
 /**
@@ -115,8 +121,10 @@
     private boolean mCheckingAccessRestrictionInProgress = false;
     protected long mTimeoutMillis = 0;
     private final long mOemEnabledTimeoutMillis;
-    private final AtomicBoolean mIsSatelliteConnectedViaCarrierWithinHysteresisTime =
+    protected final AtomicBoolean mIsSatelliteConnectedViaCarrierWithinHysteresisTime =
             new AtomicBoolean(false);
+    protected final AtomicInteger mSubIdOfSatelliteConnectedViaCarrierWithinHysteresisTime =
+            new AtomicInteger(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
     @GuardedBy("mLock")
     private boolean mIsTimerTimedOut = false;
     protected int mCountOfTimerStarted = 0;
@@ -234,8 +242,7 @@
          * should do this check now so that we have higher chance of sending the event
          * EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer.
          */
-        mIsSatelliteConnectedViaCarrierWithinHysteresisTime.set(
-                mSatelliteController.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+        updateSatelliteConnectedViaCarrierWithinHysteresisTimeState();
         sendMessage(obtainMessage(EVENT_EMERGENCY_CALL_STARTED, connection));
     }
 
@@ -262,6 +269,27 @@
         return SmsApplication.getDefaultSendToApplication(mContext, false);
     }
 
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected boolean updateAndGetProvisionState() {
+        mSatelliteController.updateSatelliteProvisionedStatePerSubscriberId();
+        return isDeviceProvisioned();
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected boolean isSatelliteAllowedByReasons() {
+        SatelliteManager satelliteManager = mContext.getSystemService(SatelliteManager.class);
+        int[] disallowedReasons = satelliteManager.getSatelliteDisallowedReasons();
+        if (Arrays.stream(disallowedReasons).anyMatch(r ->
+                (r == SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP
+                        || r == SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED
+                        || r == SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED))) {
+            plogd("isAllowedForDefaultMessageApp:false, disallowedReasons="
+                    + Arrays.toString(disallowedReasons));
+            return false;
+        }
+        return true;
+    }
+
     private void handleEmergencyCallStartedEvent(@NonNull Connection connection) {
         plogd("handleEmergencyCallStartedEvent: connection=" + connection);
         mSatelliteController.setLastEmergencyCallTime();
@@ -284,8 +312,9 @@
     }
 
     private void handleSatelliteProvisionStateChangedEvent(boolean provisioned) {
-        if (!provisioned) {
-            cleanUpResources();
+        if (!provisioned
+                && !isSatelliteConnectedViaCarrierWithinHysteresisTime()) {
+            cleanUpResources(false);
         }
     }
 
@@ -310,6 +339,8 @@
                 return;
             }
 
+            updateAndGetProvisionState();
+
             /*
              * The device might be connected to satellite after the emergency call started. Thus, we
              * need to do this check again so that we will have higher chance of sending the event
@@ -321,7 +352,7 @@
             boolean isCellularAvailable = SatelliteServiceUtils.isCellularAvailable();
             if (!isCellularAvailable
                     && isSatelliteAllowed()
-                    && (isSatelliteViaOemAvailable()
+                    && ((isDeviceProvisioned() && isSatelliteAllowedByReasons())
                     || isSatelliteConnectedViaCarrierWithinHysteresisTime())
                     && shouldTrackCall(mEmergencyConnection.getState())) {
                 plogd("handleTimeoutEvent: Sent EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer");
@@ -336,8 +367,7 @@
                     + ", isCellularAvailable=" + isCellularAvailable
                     + ", isSatelliteAllowed=" + isSatelliteAllowed()
                     + ", shouldTrackCall=" + shouldTrackCall(mEmergencyConnection.getState()));
-            reportESosRecommenderDecision(isDialerNotified);
-            cleanUpResources();
+            cleanUpResources(isDialerNotified);
         }
     }
 
@@ -350,8 +380,7 @@
 
     private void updateSatelliteViaCarrierAvailability() {
         if (!mIsSatelliteConnectedViaCarrierWithinHysteresisTime.get()) {
-            mIsSatelliteConnectedViaCarrierWithinHysteresisTime.set(
-                    mSatelliteController.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+            updateSatelliteConnectedViaCarrierWithinHysteresisTimeState();
         }
     }
 
@@ -360,8 +389,8 @@
      * @return {@code true} if satellite is provisioned via OEM else return {@code false}
      */
     @VisibleForTesting
-    public boolean isSatelliteViaOemAvailable() {
-        Boolean satelliteProvisioned = mSatelliteController.isSatelliteViaOemProvisioned();
+    public boolean isDeviceProvisioned() {
+        Boolean satelliteProvisioned = mSatelliteController.isDeviceProvisioned();
         return satelliteProvisioned != null ? satelliteProvisioned : false;
     }
 
@@ -388,13 +417,12 @@
              * we're not tracking. There must be some unexpected things happened in
              * TelephonyConnectionService. Thus, we need to clean up the resources.
              */
-            cleanUpResources();
+            cleanUpResources(false);
             return;
         }
 
         if (!shouldTrackCall(state)) {
-            reportESosRecommenderDecision(false);
-            cleanUpResources();
+            cleanUpResources(false);
         } else {
             // Location service will enter emergency mode only when connection state changes to
             // STATE_DIALING
@@ -405,7 +433,8 @@
         }
     }
 
-    private void reportESosRecommenderDecision(boolean isDialerNotified) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void reportESosRecommenderDecision(boolean isDialerNotified) {
         SatelliteStats.getInstance().onSatelliteSosMessageRecommender(
                 new SatelliteStats.SatelliteSosMessageRecommenderParams.Builder()
                         .setDisplaySosMessageSent(isDialerNotified)
@@ -416,11 +445,13 @@
                         .setRecommendingHandoverType(getEmergencyCallToSatelliteHandoverType())
                         .setIsSatelliteAllowedInCurrentLocation(isSatelliteAllowed())
                         .setIsWifiConnected(mCountryDetector.isWifiNetworkConnected())
-                        .setCarrierId(getAvailableNtnCarrierID()).build());
+                        .setCarrierId(mSatelliteController.getSatelliteCarrierId())
+                        .setIsNtnOnlyCarrier(mSatelliteController.isNtnOnlyCarrier()).build());
     }
 
-    private void cleanUpResources() {
+    private void cleanUpResources(boolean isDialerNotified) {
         plogd("cleanUpResources");
+        reportESosRecommenderDecision(isDialerNotified);
         synchronized (mLock) {
             stopTimer();
             if (mEmergencyConnection != null) {
@@ -543,10 +574,19 @@
 
     private void selectEmergencyCallWaitForConnectionTimeoutDuration() {
         if (isSatelliteConnectedViaCarrierWithinHysteresisTime()) {
+            int satelliteSubId = mSubIdOfSatelliteConnectedViaCarrierWithinHysteresisTime.get();
             mTimeoutMillis =
-                    mSatelliteController.getCarrierEmergencyCallWaitForConnectionTimeoutMillis();
+                    mSatelliteController.getCarrierEmergencyCallWaitForConnectionTimeoutMillis(
+                            satelliteSubId);
         } else {
-            mTimeoutMillis = mOemEnabledTimeoutMillis;
+            int satelliteSubId = mSatelliteController.getSelectedSatelliteSubId();
+            if (!SatelliteServiceUtils.isNtnOnlySubscriptionId(satelliteSubId)) {
+                mTimeoutMillis =
+                    mSatelliteController.getCarrierEmergencyCallWaitForConnectionTimeoutMillis(
+                        satelliteSubId);
+            } else {
+                mTimeoutMillis = mOemEnabledTimeoutMillis;
+            }
         }
         plogd("mTimeoutMillis = " + mTimeoutMillis);
     }
@@ -645,7 +685,7 @@
 
     @NonNull private Bundle createExtraBundleForEventDisplayEmergencyMessage(
             boolean isTestEmergencyNumber) {
-        int handoverType = EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
+        int handoverType = getEmergencyCallToSatelliteHandoverType();
         Pair<String, String> oemSatelliteMessagingApp =
                 getOemEnabledSatelliteHandoverAppFromOverlayConfig(mContext);
         String packageName = oemSatelliteMessagingApp.first;
@@ -653,10 +693,8 @@
         String action = getSatelliteEmergencyHandoverIntentActionFromOverlayConfig(mContext,
                 isTestEmergencyNumber);
 
-        if (isSatelliteConnectedViaCarrierWithinHysteresisTime()
-                || isEmergencyCallToSatelliteHandoverTypeT911Enforced()) {
+        if (handoverType == EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911) {
             ComponentName defaultSmsAppComponent = getDefaultSmsApp();
-            handoverType = EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911;
             packageName = defaultSmsAppComponent.getPackageName();
             className = defaultSmsAppComponent.getClassName();
         }
@@ -737,20 +775,18 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     public int getEmergencyCallToSatelliteHandoverType() {
-        if (Flags.carrierRoamingNbIotNtn() && isSatelliteViaOemAvailable()
-                && isSatelliteConnectedViaCarrierWithinHysteresisTime()) {
-            Phone satellitePhone = mSatelliteController.getSatellitePhone();
-            if (satellitePhone == null) {
-                ploge("getEmergencyCallToSatelliteHandoverType: satellitePhone is null");
-                return EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911;
-            }
-            int satelliteSubId = satellitePhone.getSubId();
+        if (isSatelliteConnectedViaCarrierWithinHysteresisTime()) {
+            int satelliteSubId = mSubIdOfSatelliteConnectedViaCarrierWithinHysteresisTime.get();
             return mSatelliteController.getCarrierRoamingNtnEmergencyCallToSatelliteHandoverType(
                     satelliteSubId);
-        } else if (isSatelliteConnectedViaCarrierWithinHysteresisTime()) {
-            return EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911;
         } else {
-            return EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
+            int satelliteSubId = mSatelliteController.getSelectedSatelliteSubId();
+            if (!SatelliteServiceUtils.isNtnOnlySubscriptionId(satelliteSubId)) {
+                return mSatelliteController
+                    .getCarrierRoamingNtnEmergencyCallToSatelliteHandoverType(satelliteSubId);
+            } else {
+                return EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
+            }
         }
     }
 
@@ -804,10 +840,23 @@
     }
 
     private boolean isSatelliteViaOemProvisioned() {
-        Boolean provisioned = mSatelliteController.isSatelliteViaOemProvisioned();
+        Boolean provisioned = mSatelliteController.isDeviceProvisioned();
         return (provisioned != null) && provisioned;
     }
 
+    private void updateSatelliteConnectedViaCarrierWithinHysteresisTimeState() {
+        Pair<Boolean, Integer> satelliteConnectedState =
+                mSatelliteController.isSatelliteConnectedViaCarrierWithinHysteresisTime();
+        mIsSatelliteConnectedViaCarrierWithinHysteresisTime.set(satelliteConnectedState.first);
+        if (satelliteConnectedState.first) {
+            mSubIdOfSatelliteConnectedViaCarrierWithinHysteresisTime.set(
+                    satelliteConnectedState.second);
+        } else {
+            mSubIdOfSatelliteConnectedViaCarrierWithinHysteresisTime.set(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        }
+    }
+
     private static void logv(@NonNull String log) {
         Rlog.v(TAG, log);
     }
@@ -833,23 +882,6 @@
         }
     }
 
-    /** Returns the carrier ID of NTN subscription */
-    private int getAvailableNtnCarrierID() {
-        Pair<Boolean, Integer> ntnSubInfo =
-                mSatelliteController.isUsingNonTerrestrialNetworkViaCarrier();
-        if (ntnSubInfo.first) {
-            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-            return tm.createForSubscriptionId(ntnSubInfo.second).getSimCarrierId();
-        }
-
-        Phone satellitePhone = mSatelliteController.getSatellitePhone();
-        if (satellitePhone != null) {
-            return satellitePhone.getCarrierId();
-        }
-
-        return TelephonyManager.UNKNOWN_CARRIER_ID;
-    }
-
     private void plogd(@NonNull String log) {
         Rlog.d(TAG, log);
         if (mPersistentLogger != null) {
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
index 3936a7e..2ee9759 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
@@ -36,13 +36,16 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.satellite.AntennaPosition;
+import android.telephony.satellite.EarfcnRange;
 import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.PointingInfo;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteInfo;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.SatelliteModemEnableRequestAttributes;
 import android.telephony.satellite.SatelliteSubscriptionInfo;
+import android.telephony.satellite.SystemSelectionSpecifier;
 import android.telephony.satellite.stub.NTRadioTechnology;
 import android.telephony.satellite.stub.SatelliteModemState;
 import android.telephony.satellite.stub.SatelliteResult;
@@ -363,6 +366,29 @@
     }
 
     /**
+     * Check if the subscription ID is a NTN only subscription ID.
+     *
+     * @return {@code true} if the subscription ID is a NTN only subscription ID,
+     * {@code false} otherwise.
+    */
+    public static boolean isNtnOnlySubscriptionId(int subId) {
+        SubscriptionManagerService subscriptionManagerService =
+            SubscriptionManagerService.getInstance();
+        if (subscriptionManagerService == null) {
+            logd("isNtnOnlySubscriptionId: subscriptionManagerService is null");
+            return false;
+        }
+
+        SubscriptionInfo subInfo = subscriptionManagerService.getSubscriptionInfo(subId);
+        if (subInfo == null) {
+            logd("isNtnOnlySubscriptionId: subInfo is null for subId=" + subId);
+            return false;
+        }
+
+        return subInfo.isOnlyNonTerrestrialNetwork();
+    }
+
+    /**
      * Expected format of the input dictionary bundle is:
      * <ul>
      *     <li>Key: PLMN string.</li>
@@ -542,6 +568,100 @@
         return mcc + mnc;
     }
 
+    @NonNull
+    private static android.telephony.satellite.stub
+            .SystemSelectionSpecifier convertSystemSelectionSpecifierToHALFormat(
+            @NonNull SystemSelectionSpecifier systemSelectionSpecifier) {
+        android.telephony.satellite.stub.SystemSelectionSpecifier convertedSpecifier =
+                new android.telephony.satellite.stub.SystemSelectionSpecifier();
+
+        convertedSpecifier.mMccMnc = systemSelectionSpecifier.getMccMnc();
+        convertedSpecifier.mBands = systemSelectionSpecifier.getBands();
+        convertedSpecifier.mEarfcs = systemSelectionSpecifier.getEarfcns();
+        SatelliteInfo[] satelliteInfos = systemSelectionSpecifier.getSatelliteInfos()
+                .toArray(new SatelliteInfo[0]);
+        android.telephony.satellite.stub.SatelliteInfo[] halSatelliteInfos =
+                new android.telephony.satellite.stub.SatelliteInfo[satelliteInfos.length];
+        for (int i = 0; i < satelliteInfos.length; i++) {
+            halSatelliteInfos[i] = new android.telephony.satellite.stub.SatelliteInfo();
+
+            halSatelliteInfos[i].id = new android.telephony.satellite.stub.UUID();
+            halSatelliteInfos[i].id.mostSigBits =
+                    satelliteInfos[i].getSatelliteId().getMostSignificantBits();
+            halSatelliteInfos[i].id.leastSigBits =
+                    satelliteInfos[i].getSatelliteId().getLeastSignificantBits();
+
+            halSatelliteInfos[i].position =
+                    new android.telephony.satellite.stub.SatellitePosition();
+            halSatelliteInfos[i].position.longitudeDegree =
+                    satelliteInfos[i].getSatellitePosition().getLongitudeDegrees();
+            halSatelliteInfos[i].position.altitudeKm =
+                    satelliteInfos[i].getSatellitePosition().getAltitudeKm();
+
+            halSatelliteInfos[i].bands = satelliteInfos[i].getBands().stream().mapToInt(
+                    Integer::intValue).toArray();
+
+            List<EarfcnRange> earfcnRangeList = satelliteInfos[i].getEarfcnRanges();
+            halSatelliteInfos[i].earfcnRanges =
+                    new android.telephony.satellite.stub.EarfcnRange[earfcnRangeList.size()];
+            for (int j = 0; j < earfcnRangeList.size(); j++) {
+                halSatelliteInfos[i].earfcnRanges[j] =
+                        new android.telephony.satellite.stub.EarfcnRange();
+                halSatelliteInfos[i].earfcnRanges[j].startEarfcn = earfcnRangeList.get(
+                        j).getStartEarfcn();
+                halSatelliteInfos[i].earfcnRanges[j].endEarfcn = earfcnRangeList.get(
+                        j).getEndEarfcn();
+            }
+        }
+        convertedSpecifier.satelliteInfos = halSatelliteInfos;
+        convertedSpecifier.tagIds = systemSelectionSpecifier.getTagIds();
+        return convertedSpecifier;
+    }
+
+    /**
+     * Convert SystemSelectionSpecifier from framework definition to service definition
+     * @param systemSelectionSpecifier The SystemSelectionSpecifier from the framework.
+     * @return The converted SystemSelectionSpecifier for the satellite service.
+     */
+    @NonNull
+    public static List<android.telephony.satellite.stub
+            .SystemSelectionSpecifier> toSystemSelectionSpecifier(
+            @NonNull List<SystemSelectionSpecifier> systemSelectionSpecifier) {
+        return systemSelectionSpecifier.stream().map(
+                SatelliteServiceUtils::convertSystemSelectionSpecifierToHALFormat).collect(
+                Collectors.toList());
+    }
+
+    /**
+     * Expected format of the input dictionary bundle is:
+     * <ul>
+     *     <li>Key: Regional satellite config Id string.</li>
+     *     <li>Value: Integer arrays of earfcns in the corresponding regions."</li>
+     * </ul>
+     * @return The map of earfcns with key: regional satellite config Id,
+     * value: set of earfcns in the corresponding regions.
+     */
+    @NonNull
+    public static Map<String, Set<Integer>> parseRegionalSatelliteEarfcns(
+            @Nullable PersistableBundle earfcnsBundle) {
+        Map<String, Set<Integer>> earfcnsMap = new HashMap<>();
+        if (earfcnsBundle == null || earfcnsBundle.isEmpty()) {
+            logd("parseRegionalSatelliteEarfcns: earfcnsBundle is null or empty");
+            return earfcnsMap;
+        }
+
+        for (String configId : earfcnsBundle.keySet()) {
+            Set<Integer> earfcnsSet = new HashSet<>();
+            for (int earfcn : earfcnsBundle.getIntArray(configId)) {
+                earfcnsSet.add(earfcn);
+            }
+            logd("parseRegionalSatelliteEarfcns: configId = " + configId + ", earfcns ="
+                    + earfcnsSet.stream().map(String::valueOf).collect(joining(",")));
+            earfcnsMap.put(configId, earfcnsSet);
+        }
+        return earfcnsMap;
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
index f4208f7..ed3129a 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
@@ -19,6 +19,9 @@
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT;
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_SMS;
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE;
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_UNKNOWN;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE;
@@ -28,9 +31,11 @@
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_ENABLING_SATELLITE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_IDLE;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_LISTENING;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED;
@@ -38,20 +43,26 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AlarmManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.AsyncResult;
 import android.os.Build;
+import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.WorkSource;
 import android.telephony.DropBoxManagerLoggerBackend;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PersistentLogger;
+import android.telephony.ServiceState;
 import android.telephony.satellite.ISatelliteModemStateCallback;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.stub.ISatelliteGateway;
@@ -60,14 +71,15 @@
 import android.util.Log;
 
 import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.DeviceStateMonitor;
 import com.android.internal.telephony.ExponentialBackoff;
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
+import com.android.internal.telephony.util.ArrayUtils;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.telephony.Rlog;
@@ -123,7 +135,10 @@
     private static final int EVENT_SATELLITE_ENABLEMENT_FAILED = 8;
     private static final int EVENT_SCREEN_STATE_CHANGED = 9;
     protected static final int EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT = 10;
-    protected static final int EVENT_CARRIER_ROAMING_NB_IOT_INACTIVITY_TIMER_TIMED_OUT = 11;
+    protected static final int EVENT_ESOS_INACTIVITY_TIMER_TIMED_OUT = 11;
+    private static final int EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE = 12;
+    private static final int EVENT_SERVICE_STATE_CHANGED = 13;
+    protected static final int EVENT_P2P_SMS_INACTIVITY_TIMER_TIMED_OUT = 14;
 
     private static final long REBIND_INITIAL_DELAY = 2 * 1000; // 2 seconds
     private static final long REBIND_MAXIMUM_DELAY = 64 * 1000; // 1 minute
@@ -131,6 +146,7 @@
     private static final int DEFAULT_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC = 30;
     private static final int DEFAULT_P2P_SMS_INACTIVITY_TIMEOUT_SEC = 180;
     private static final int DEFAULT_ESOS_INACTIVITY_TIMEOUT_SEC = 600;
+    private static final long UNDEFINED_TIMESTAMP = 0L;
 
     @NonNull private final ExponentialBackoff mExponentialBackoff;
     @NonNull private final Object mLock = new Object();
@@ -160,6 +176,7 @@
     private long mSatelliteStayAtListeningFromSendingMillis;
     private long mSatelliteStayAtListeningFromReceivingMillis;
     private long mSatelliteNbIotInactivityTimeoutMillis;
+    private boolean mIgnoreCellularServiceState = false;
     private final ConcurrentHashMap<IBinder, ISatelliteModemStateCallback> mListeners;
     @SatelliteManager.SatelliteModemState private int mCurrentState;
     @SatelliteManager.SatelliteModemState private int mPreviousState;
@@ -168,16 +185,27 @@
     // Interested in screen off, so use default value true
     boolean mIsScreenOn = true;
     private boolean mIsDeviceAlignedWithSatellite = false;
+    private long mInactivityStartTimestamp = UNDEFINED_TIMESTAMP;
+    private DatagramTransferState mLastDatagramTransferState =
+            new DatagramTransferState(
+                    SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN,
+                    SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN,
+                    DATAGRAM_TYPE_UNKNOWN);
 
-    @GuardedBy("mLock")
-    @NonNull private boolean mIsDisableCellularModemInProgress = false;
     @NonNull private final SatelliteController mSatelliteController;
     @NonNull private final DatagramController mDatagramController;
     @Nullable private PersistentLogger mPersistentLogger = null;
     @Nullable private DeviceStateMonitor mDeviceStateMonitor;
     @NonNull private SessionMetricsStats mSessionMetricsStats;
-
     @NonNull private FeatureFlags mFeatureFlags;
+    @NonNull private AlarmManager mAlarmManager;
+    private final AlarmManager.OnAlarmListener mAlarmListener = new AlarmManager.OnAlarmListener() {
+        @Override
+        public void onAlarm() {
+            plogd("onAlarm: screen off timer expired");
+            sendMessage(EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT);
+        }
+    };
 
     /**
      * @return The singleton instance of SatelliteSessionController.
@@ -205,8 +233,10 @@
             boolean isSatelliteSupported) {
         if (sInstance == null || isSatelliteSupported != sInstance.mIsSatelliteSupported) {
             ConcurrentHashMap<IBinder, ISatelliteModemStateCallback> existingListeners = null;
+            boolean existIgnoreCellularServiceState = false;
             if (sInstance != null) {
                 existingListeners = sInstance.mListeners;
+                existIgnoreCellularServiceState = sInstance.mIgnoreCellularServiceState;
                 sInstance.cleanUpResource();
             }
 
@@ -220,6 +250,10 @@
                 Log.d(TAG, "make() existingListeners: " + existingListeners.size());
                 sInstance.mListeners.putAll(existingListeners);
             }
+            if (existIgnoreCellularServiceState) {
+                Log.d(TAG, "make() existIgnoreCellularServiceState is true");
+                sInstance.mIgnoreCellularServiceState = true;
+            }
         }
         return sInstance;
     }
@@ -277,12 +311,13 @@
             bindService();
         });
 
-        Phone phone = mSatelliteController.getSatellitePhone();
-        if (phone == null) {
-            phone = SatelliteServiceUtils.getPhone();
+        Phone satellitePhone = mSatelliteController.getSatellitePhone();
+        if (satellitePhone == null) {
+            satellitePhone = SatelliteServiceUtils.getPhone();
         }
-        mDeviceStateMonitor = phone.getDeviceStateMonitor();
+        mDeviceStateMonitor = satellitePhone.getDeviceStateMonitor();
         mSessionMetricsStats = SessionMetricsStats.getInstance();
+        mAlarmManager = mContext.getSystemService(AlarmManager.class);
 
         addState(mUnavailableState);
         addState(mPowerOffState);
@@ -307,12 +342,23 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void onDatagramTransferStateChanged(
             @SatelliteManager.SatelliteDatagramTransferState int sendState,
-            @SatelliteManager.SatelliteDatagramTransferState int receiveState) {
-        sendMessage(EVENT_DATAGRAM_TRANSFER_STATE_CHANGED,
-                new DatagramTransferState(sendState, receiveState));
+            @SatelliteManager.SatelliteDatagramTransferState int receiveState,
+            @SatelliteManager.DatagramType int datagramType) {
+        DatagramTransferState datagramTransferState =
+                new DatagramTransferState(sendState, receiveState, datagramType);
+        mLastDatagramTransferState = datagramTransferState;
+
+        sendMessage(EVENT_DATAGRAM_TRANSFER_STATE_CHANGED, datagramTransferState);
+
         if (sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING) {
             mIsSendingTriggeredDuringTransferringState.set(true);
         }
+
+        if (datagramTransferState.isIdle()) {
+            checkForInactivity();
+        } else {
+            endUserInactivity();
+        }
     }
 
     /**
@@ -450,6 +496,23 @@
     }
 
     /**
+     * This API can be used by only CTS to control ingoring cellular service state event.
+     *
+     * @param enabled Whether to enable boolean config.
+     * @return {@code true} if the value is set successfully, {@code false} otherwise.
+     */
+    public boolean setSatelliteIgnoreCellularServiceState(boolean enabled) {
+        plogd("setSatelliteIgnoreCellularServiceState : "
+                + "old = " + mIgnoreCellularServiceState + " new : " + enabled);
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            return false;
+        }
+
+        mIgnoreCellularServiceState = enabled;
+        return true;
+    }
+
+    /**
      * This API can be used by only CTS to update satellite gateway service package name.
      *
      * @param servicePackageName The package name of the satellite gateway service.
@@ -509,13 +572,20 @@
         }
 
         mIsDeviceAlignedWithSatellite = isAligned;
+        plogd("setDeviceAlignedWithSatellite: isAligned " +  isAligned);
 
         if (mIsDeviceAlignedWithSatellite) {
-            stopCarrierRoamingNbIotInactivityTimer();
+            stopEsosInactivityTimer();
+            stopP2pSmsInactivityTimer();
+            endUserInactivity();
         } else {
-            if (mCurrentState == SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED) {
-                evaluateStartingCarrierRoamingNbIotInactivityTimer();
+            if (mCurrentState == SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED
+                    || mCurrentState == SATELLITE_MODEM_STATE_CONNECTED
+                    || mCurrentState == SATELLITE_MODEM_STATE_IDLE) {
+                evaluateStartingEsosInactivityTimer();
+                evaluateStartingP2pSmsInactivityTimer();
             }
+            checkForInactivity();
         }
     }
 
@@ -546,10 +616,48 @@
     public void cleanUpResource() {
         plogd("cleanUpResource");
         mIsDeviceAlignedWithSatellite = false;
+        mInactivityStartTimestamp = UNDEFINED_TIMESTAMP;
         unregisterForScreenStateChanged();
+        if (mAlarmManager != null) {
+            mAlarmManager.cancel(mAlarmListener);
+        }
+
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            // Register to received Cellular service state
+            for (Phone phone : PhoneFactory.getPhones()) {
+                if (phone == null) continue;
+
+                phone.unregisterForServiceStateChanged(getHandler());
+                if (DBG) {
+                    plogd("cleanUpResource: unregisterForServiceStateChanged phoneId "
+                            + phone.getPhoneId());
+                }
+            }
+        }
+
         quitNow();
     }
 
+    /**
+     * Uses this function to notify that cellular service state has changed
+     *
+     * @param serviceState The state of the cellular service.
+     */
+    @VisibleForTesting
+    public void onCellularServiceStateChanged(ServiceState serviceState) {
+        sendMessage(EVENT_SERVICE_STATE_CHANGED, new AsyncResult(null, serviceState, null));
+    }
+
+    /**
+     * Uses this function to set AlarmManager object for testing.
+     *
+     * @param alarmManager The instance of AlarmManager.
+     */
+    @VisibleForTesting
+    public void setAlarmManager(AlarmManager alarmManager) {
+        mAlarmManager = alarmManager;
+    }
+
     private boolean isDemoMode() {
         return mIsDemoMode;
     }
@@ -557,11 +665,19 @@
     private static class DatagramTransferState {
         @SatelliteManager.SatelliteDatagramTransferState public int sendState;
         @SatelliteManager.SatelliteDatagramTransferState public int receiveState;
+        @SatelliteManager.DatagramType public int datagramType;
 
         DatagramTransferState(@SatelliteManager.SatelliteDatagramTransferState int sendState,
-                @SatelliteManager.SatelliteDatagramTransferState int receiveState) {
+                @SatelliteManager.SatelliteDatagramTransferState int receiveState,
+                @SatelliteManager.DatagramType int datagramType) {
             this.sendState = sendState;
             this.receiveState = receiveState;
+            this.datagramType = datagramType;
+        }
+
+        public boolean isIdle() {
+            return (sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE
+                    && receiveState == SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
         }
     }
 
@@ -591,11 +707,11 @@
             mPreviousState = mCurrentState;
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_OFF;
             mIsSendingTriggeredDuringTransferringState.set(false);
-            synchronized (mLock) {
-                mIsDisableCellularModemInProgress = false;
-            }
             unbindService();
             stopNbIotInactivityTimer();
+            stopEsosInactivityTimer();
+            stopP2pSmsInactivityTimer();
+            endUserInactivity();
             DemoSimulator.getInstance().onSatelliteModeOff();
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_OFF);
 
@@ -790,9 +906,24 @@
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_IDLE;
             mIsSendingTriggeredDuringTransferringState.set(false);
             stopNbIotInactivityTimer();
+
             //Enable Cellular Modem scanning
-            mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(true, null);
-            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+            boolean configSatelliteAllowTnScanningDuringSatelliteSession =
+                    isTnScanningAllowedDuringSatelliteSession();
+            if (configSatelliteAllowTnScanningDuringSatelliteSession) {
+                Message onCompleted =
+                    obtainMessage(EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE);
+                mSatelliteModemInterface
+                    .enableCellularModemWhileSatelliteModeIsOn(true, onCompleted);
+            } else {
+                plogd("Device does not allow cellular modem scanning");
+            }
+            if (isConcurrentTnScanningSupported()
+                    || !configSatelliteAllowTnScanningDuringSatelliteSession) {
+                plogd("IDLE state is hidden from clients");
+            } else {
+                notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+            }
         }
 
         @Override
@@ -807,7 +938,7 @@
                     break;
                 case EVENT_DISABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE:
                     handleEventDisableCellularModemWhileSatelliteModeIsOnDone(
-                            (AsyncResult) msg.obj);
+                        (AsyncResult) msg.obj);
                     break;
                 case EVENT_SATELLITE_ENABLEMENT_STARTED:
                     handleSatelliteEnablementStarted((boolean) msg.obj);
@@ -821,6 +952,22 @@
                 case EVENT_SATELLITE_MODEM_STATE_CHANGED:
                     handleSatelliteModemStateChanged(msg);
                     break;
+                case EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE:
+                    plogd("EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE");
+                    break;
+                case EVENT_SERVICE_STATE_CHANGED:
+                    if (!mIgnoreCellularServiceState) {
+                        AsyncResult ar = (msg.obj != null) ? (AsyncResult) msg.obj : null;
+                        if (ar == null || ar.result == null) {
+                            plogd("IdleState: processing: can't access ServiceState");
+                        } else {
+                            ServiceState newServiceState = (ServiceState) ar.result;
+                            handleEventServiceStateChanged(newServiceState);
+                        }
+                    } else {
+                        plogd("IdleState: processing: ignore EVENT_SERVICE_STATE_CHANGED");
+                    }
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -831,68 +978,86 @@
             if ((datagramTransferState.sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING)
                     || (datagramTransferState.receiveState
                     == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING)) {
-                if (mSatelliteController.isSatelliteAttachRequired()) {
-                    ploge("Unexpected transferring state received for NB-IOT NTN");
-                } else {
-                    transitionTo(mTransferringState);
-                }
+                transitionTo(mTransferringState);
             } else if ((datagramTransferState.sendState
                     == SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT)
                     || (datagramTransferState.receiveState
                     == SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT)) {
                 if (mSatelliteController.isSatelliteAttachRequired()) {
-                    disableCellularModemWhileSatelliteModeIsOn();
+                    transitionTo(mNotConnectedState);
                 } else {
                     ploge("Unexpected transferring state received for non-NB-IOT NTN");
                 }
             }
         }
 
-        private void handleEventDisableCellularModemWhileSatelliteModeIsOnDone(
-                @NonNull AsyncResult result) {
-            synchronized (mLock) {
-                if (mIsDisableCellularModemInProgress) {
-                    int error = SatelliteServiceUtils.getSatelliteError(
-                            result, "DisableCellularModemWhileSatelliteModeIsOnDone");
-                    if (error == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
-                        transitionTo(mNotConnectedState);
-                    }
-                    mIsDisableCellularModemInProgress = false;
-                } else {
-                    ploge("DisableCellularModemWhileSatelliteModeIsOn is not in progress");
+        private void handleEventServiceStateChanged(ServiceState serviceState) {
+            boolean isInServiceOrEmergency =
+                    serviceState.getVoiceRegState() == ServiceState.STATE_IN_SERVICE
+                    || serviceState.getDataRegState() == ServiceState.STATE_IN_SERVICE
+                    || serviceState.isEmergencyOnly();
+            if (!isInServiceOrEmergency) {
+                plogd("handleEventServiceStateChanged: is not IN_SERVICE or EMERGENCY_ONLY");
+                return;
+            }
+
+            // In emergency
+            boolean isEmergency = mSatelliteController.getRequestIsEmergency();
+            if (isEmergency) {
+                boolean isEmergencyCommunicationEstablished = (mDatagramController == null)
+                        ? false : mDatagramController.isEmergencyCommunicationEstablished();
+                boolean isTurnOffAllowed =
+                        mSatelliteController.turnOffSatelliteSessionForEmergencyCall(getSubId());
+                if (isEmergencyCommunicationEstablished || !isTurnOffAllowed) {
+                    logd("handleEventServiceStateChanged: "
+                            + "can't disable emergency satellite session");
+                    return;
                 }
             }
+
+            mSatelliteController.requestSatelliteEnabled(
+                    false /*enableSatellite*/,
+                    false /*enableDemoMode*/,
+                    isEmergency /*isEmergency*/,
+                    new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            plogd("requestSatelliteEnabled result=" + result);
+                            if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
+                                mSessionMetricsStats.addCountOfAutoExitDueToTnNetwork();
+                            }
+                        }
+                    });
+        }
+
+        private void handleEventDisableCellularModemWhileSatelliteModeIsOnDone(
+                @NonNull AsyncResult result) {
+            int error = SatelliteServiceUtils.getSatelliteError(
+                        result, "DisableCellularModemWhileSatelliteModeIsOnDone");
+            plogd("Disable TN scanning done with result: " + error);
         }
 
         private void handleSatelliteModemStateChanged(@NonNull Message msg) {
             int state = msg.arg1;
             if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF) {
                 transitionTo(mPowerOffState);
-            }
-        }
-
-        private void disableCellularModemWhileSatelliteModeIsOn() {
-            synchronized (mLock) {
-                if (mIsDisableCellularModemInProgress) {
-                    plogd("Cellular scanning is already being disabled");
-                    return;
+            } else if (state == SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED
+                           || state == SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED) {
+                if (isConcurrentTnScanningSupported()) {
+                    plogd("Notifying the new state " + state + " to clients but still"
+                            + " stay at IDLE state internally");
+                    notifyStateChangedEvent(state);
+                } else {
+                    plogd("Ignoring the modem state " + state);
                 }
-
-                mIsDisableCellularModemInProgress = true;
-                Message onCompleted =
-                        obtainMessage(EVENT_DISABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE);
-                mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(false,
-                        onCompleted);
             }
         }
 
         @Override
         public void exit() {
             if (DBG) plogd("Exiting IdleState");
-            if (!mSatelliteController.isSatelliteAttachRequired()) {
-                // Disable cellular modem scanning
-                mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(false, null);
-            }
+            // Disable cellular modem scanning
+            mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(false, null);
         }
     }
 
@@ -901,6 +1066,9 @@
         public void enter() {
             if (DBG) plogd("Entering TransferringState");
             stopNbIotInactivityTimer();
+            stopEsosInactivityTimer();
+            stopP2pSmsInactivityTimer();
+
             mPreviousState = mCurrentState;
             mCurrentState = SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
             notifyStateChangedEvent(SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
@@ -1053,14 +1221,13 @@
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED;
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
             startNbIotInactivityTimer();
-            evaluateStartingCarrierRoamingNbIotInactivityTimer();
+            evaluateStartingEsosInactivityTimer();
+            evaluateStartingP2pSmsInactivityTimer();
         }
 
         @Override
         public void exit() {
             if (DBG) plogd("Exiting NotConnectedState");
-
-            stopCarrierRoamingNbIotInactivityTimer();
         }
 
         @Override
@@ -1074,8 +1241,17 @@
                 case EVENT_SATELLITE_MODEM_STATE_CHANGED:
                     handleEventSatelliteModemStateChanged(msg.arg1);
                     break;
-                case EVENT_CARRIER_ROAMING_NB_IOT_INACTIVITY_TIMER_TIMED_OUT:
-                    // fall through
+                case EVENT_ESOS_INACTIVITY_TIMER_TIMED_OUT:
+                    if (isP2pSmsInActivityTimerStarted()) {
+                        plogd("NotConnectedState: processing: P2P_SMS inactivity timer running "
+                                + "can not move to IDLE");
+                    } else {
+                        transitionTo(mIdleState);
+                    }
+                    break;
+                case EVENT_P2P_SMS_INACTIVITY_TIMER_TIMED_OUT:
+                    handleEventP2pSmsInactivityTimerTimedOut();
+                    break;
                 case EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT:
                     transitionTo(mIdleState);
                     break;
@@ -1112,17 +1288,29 @@
                     || datagramTransferState.receiveState
                     == SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT) {
                 stopNbIotInactivityTimer();
-                stopCarrierRoamingNbIotInactivityTimer();
+
+                if (mSatelliteController.getRequestIsEmergency()) {
+                    stopEsosInactivityTimer();
+                }
+                stopP2pSmsInactivityTimer();
             } else if (datagramTransferState.sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE
                     && datagramTransferState.receiveState
                     == SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE) {
                 startNbIotInactivityTimer();
-                evaluateStartingCarrierRoamingNbIotInactivityTimer();
+                evaluateStartingEsosInactivityTimer();
+                evaluateStartingP2pSmsInactivityTimer();
             } else if (isSending(datagramTransferState.sendState)
                     || isReceiving(datagramTransferState.receiveState)) {
-                restartNbIotInactivityTimer();
-                stopCarrierRoamingNbIotInactivityTimer();
-                evaluateStartingCarrierRoamingNbIotInactivityTimer();
+                stopNbIotInactivityTimer();
+
+                int datagramType = datagramTransferState.datagramType;
+                if (datagramType == DATAGRAM_TYPE_SOS_MESSAGE) {
+                    stopEsosInactivityTimer();
+                } else if (datagramType == DATAGRAM_TYPE_SMS) {
+                    stopP2pSmsInactivityTimer();
+                } else {
+                    plogd("datagram type is not SOS_Message and SMS " + datagramType);
+                }
             }
         }
     }
@@ -1136,6 +1324,10 @@
             mCurrentState = SATELLITE_MODEM_STATE_CONNECTED;
             notifyStateChangedEvent(SATELLITE_MODEM_STATE_CONNECTED);
             startNbIotInactivityTimer();
+            evaluateStartingEsosInactivityTimer();
+            evaluateStartingP2pSmsInactivityTimer();
+            mSatelliteController.updateLastNotifiedCarrierRoamingNtnSignalStrengthAndNotify(
+                    mSatelliteController.getSatellitePhone());
         }
 
         @Override
@@ -1169,6 +1361,17 @@
                 case EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT:
                     handleEventScreenOffInactivityTimerTimedOut();
                     break;
+                case EVENT_ESOS_INACTIVITY_TIMER_TIMED_OUT:
+                    if (isP2pSmsInActivityTimerStarted()) {
+                        plogd("ConnectedState: processing: P2P_SMS inactivity timer running "
+                                + "can not move to IDLE");
+                    } else {
+                        transitionTo(mIdleState);
+                    }
+                    break;
+                case EVENT_P2P_SMS_INACTIVITY_TIMER_TIMED_OUT:
+                    handleEventP2pSmsInactivityTimerTimedOut();
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -1229,8 +1432,17 @@
             case EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT:
                 whatString = "EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT";
                 break;
-            case EVENT_CARRIER_ROAMING_NB_IOT_INACTIVITY_TIMER_TIMED_OUT:
-                whatString = "EVENT_CARRIER_ROAMING_NB_IOT_INACTIVITY_TIMER_TIMED_OUT";
+            case EVENT_ESOS_INACTIVITY_TIMER_TIMED_OUT:
+                whatString = "EVENT_ESOS_INACTIVITY_TIMER_TIMED_OUT";
+                break;
+            case EVENT_P2P_SMS_INACTIVITY_TIMER_TIMED_OUT:
+                whatString = "EVENT_P2P_SMS_INACTIVITY_TIMER_TIMED_OUT";
+                break;
+            case EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE:
+                whatString = "EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE";
+                break;
+            case EVENT_SERVICE_STATE_CHANGED:
+                whatString = "EVENT_SERVICE_STATE_CHANGED";
                 break;
             default:
                 whatString = "UNKNOWN EVENT " + what;
@@ -1247,12 +1459,7 @@
     }
 
     private int getSubId() {
-        Phone phone = mSatelliteController.getSatellitePhone();
-        if (phone == null) {
-            return SatelliteServiceUtils.getPhone().getSubId();
-        }
-
-        return phone.getSubId();
+        return mSatelliteController.getSelectedSatelliteSubId();
     }
 
     private void notifyStateChangedEvent(@SatelliteManager.SatelliteModemState int state) {
@@ -1407,12 +1614,6 @@
             return;
         }
 
-        if (mSatelliteController.getRequestIsEmergency()) {
-            if (DBG) logd("registerScreenOnOffChanged: Emergency mode");
-            // screen on/off timer is available in not emergency mode
-            return;
-        }
-
         if (!mIsRegisteredScreenStateChanged && mDeviceStateMonitor != null) {
             mDeviceStateMonitor.registerForScreenStateChanged(
                     getHandler(), EVENT_SCREEN_STATE_CHANGED, null);
@@ -1458,20 +1659,80 @@
         }
         mIsScreenOn = screenOn;
 
+        if (!mSatelliteController.isInCarrierRoamingNbIotNtn()) {
+            logd("handleEventScreenStateChanged: device is not in CarrierRoamingNbIotNtn");
+            return;
+        }
+
+        if (mSatelliteController.getRequestIsEmergency()) {
+            if (DBG) logd("handleEventScreenStateChanged: Emergency mode");
+            // This is for coexistence
+            // emergency mode can be set after registerForScreenStateChanged() called for P2P-sms
+            return;
+        }
+
+        int subId = getSubId();
+        if (!isP2pSmsSupportedOnCarrierRoamingNtn(subId)) {
+            if (DBG) plogd("handleEventScreenStateChanged: P2P_SMS is not supported");
+            return;
+        }
+
         if (!screenOn) {
             // Screen off, start timer
             int timeoutMillis = getScreenOffInactivityTimeoutDurationSec() * 1000;
-            sendMessageDelayed(EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT, timeoutMillis);
 
+            if (mAlarmManager == null) {
+                plogd("handleEventScreenStateChanged: can not access AlarmManager to start timer");
+                return;
+            }
+
+            mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                    SystemClock.elapsedRealtime() + timeoutMillis,
+                    TAG, new HandlerExecutor(getHandler()), new WorkSource(), mAlarmListener);
             plogd("handleEventScreenStateChanged: start timer " + timeoutMillis);
         } else {
             // Screen on, stop timer
             removeMessages(EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT);
 
+            if (mAlarmManager == null) {
+                plogd("handleEventScreenStateChanged: can not access AlarmManager to stop timer");
+                return;
+            }
+
+            mAlarmManager.cancel(mAlarmListener);
             plogd("handleEventScreenStateChanged: stop timer");
         }
     }
 
+    private void handleEventP2pSmsInactivityTimerTimedOut() {
+        if (isEsosInActivityTimerStarted()) {
+            plogd("handleEventP2pSmsInactivityTimerTimedOut: processing: ESOS inactivity timer "
+                    + "running can not move to IDLE");
+        } else {
+            if (isTnScanningAllowedDuringSatelliteSession()) {
+                plogd("handleEventP2pSmsInactivityTimerTimedOut: Transition to IDLE state");
+                transitionTo(mIdleState);
+            } else {
+                if (mSatelliteController.getRequestIsEmergency()) {
+                    plogd("handleEventP2pSmsInactivityTimerTimedOut: Emergency mode");
+                    return;
+                }
+
+                plogd("handleEventP2pSmsInactivityTimerTimedOut: request disable satellite");
+                mSatelliteController.requestSatelliteEnabled(
+                        false /*enableSatellite*/,
+                        false /*enableDemoMode*/,
+                        mSatelliteController.getRequestIsEmergency() /*isEmergency*/,
+                        new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                plogd("requestSatelliteEnabled result=" + result);
+                            }
+                        });
+            }
+        }
+    }
+
     private int getScreenOffInactivityTimeoutDurationSec() {
         PersistableBundle config = mSatelliteController.getPersistableBundle(getSubId());
 
@@ -1479,13 +1740,6 @@
                 DEFAULT_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC);
     }
 
-    private int getP2pSmsInactivityTimeoutDurationSec() {
-        PersistableBundle config = mSatelliteController.getPersistableBundle(getSubId());
-
-        return config.getInt(KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT,
-                DEFAULT_P2P_SMS_INACTIVITY_TIMEOUT_SEC);
-    }
-
     private int getEsosInactivityTimeoutDurationSec() {
         PersistableBundle config = mSatelliteController.getPersistableBundle(getSubId());
 
@@ -1493,62 +1747,172 @@
                 DEFAULT_ESOS_INACTIVITY_TIMEOUT_SEC);
     }
 
-    private void evaluateStartingCarrierRoamingNbIotInactivityTimer() {
+    private void evaluateStartingEsosInactivityTimer() {
         if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
-            plogd("evaluateStartingCarrierRoamingNbIotInactivityTimer: "
+            plogd("evaluateStartingEsosInactivityTimer: "
                     + "carrierRoamingNbIotNtn is disabled");
             return;
         }
 
+        if (isEsosInActivityTimerStarted()) {
+            plogd("isEsosInActivityTimerStarted: "
+                    + "ESOS inactivity timer already started");
+            return;
+        }
+
         int subId = getSubId();
-        if (!mSatelliteController.isSatelliteRoamingP2pSmSSupported(subId)
-                && !mSatelliteController.isSatelliteEsosSupported(subId)) {
-            plogd("evaluateStartingCarrierRoamingNbIotInactivityTimer: "
-                    + "device does not support P2P SMS and ESOS are disabled");
+        if (!mSatelliteController.isSatelliteEsosSupported(subId)) {
+            plogd("evaluateStartingEsosInactivityTimer: ESOS is not supported");
+            return;
+        }
+
+        if (!mSatelliteController.getRequestIsEmergency()) {
+            plogd("evaluateStartingEsosInactivityTimer: request is not emergency");
             return;
         }
 
         if (mIsDeviceAlignedWithSatellite) {
-            plogd("evaluateStartingCarrierRoamingNbIotInactivityTimer: "
-                    + "can't start inactivity timer due to device aligned satellite");
+            plogd("evaluateStartingEsosInactivityTimer: "
+                    + "can't start ESOS inactivity timer due to device aligned satellite");
             return;
         }
 
-        if (hasMessages(EVENT_CARRIER_ROAMING_NB_IOT_INACTIVITY_TIMER_TIMED_OUT)) {
-            plogd("evaluateStartingCarrierRoamingNbIotInactivityTimer: already started");
-            return;
-        }
-
-        int timeOutMillis;
-        if (mSatelliteController.getRequestIsEmergency()) {
-            timeOutMillis = getEsosInactivityTimeoutDurationSec() * 1000;
-        } else if (mSatelliteController.isInCarrierRoamingNbIotNtn()) {
-            timeOutMillis = getP2pSmsInactivityTimeoutDurationSec() * 1000;
-        } else {
-            plogd("evaluateStartingCarrierRoamingNbIotInactivityTimer: "
-                    + "can't start inactivity timer device is in not P2P SMS and ESOS mode");
-            return;
-        }
-
+        int timeOutMillis = getEsosInactivityTimeoutDurationSec() * 1000;
         DatagramController datagramController = DatagramController.getInstance();
         if (datagramController.isSendingInIdleState()
                 && datagramController.isPollingInIdleState()) {
-            sendMessageDelayed(EVENT_CARRIER_ROAMING_NB_IOT_INACTIVITY_TIMER_TIMED_OUT,
-                    timeOutMillis);
-            plogd("evaluateStartingCarrierRoamingNbIotInactivityTimer: start inactivity timer "
+            sendMessageDelayed(EVENT_ESOS_INACTIVITY_TIMER_TIMED_OUT, timeOutMillis);
+            plogd("evaluateStartingEsosInactivityTimer: start ESOS inactivity timer "
                     + timeOutMillis);
         } else {
-            plogd("evaluateStartingCarrierRoamingNbIotInactivityTimer: "
-                    + "can't start inactivity timer");
+            plogd("evaluateStartingEsosInactivityTimer: "
+                    + "can't start ESOS inactivity timer");
         }
     }
 
-    private void stopCarrierRoamingNbIotInactivityTimer() {
-        removeMessages(EVENT_CARRIER_ROAMING_NB_IOT_INACTIVITY_TIMER_TIMED_OUT);
-        plogd("stopCarrierRoamingNbIotInactivityTimer:");
+    private void stopEsosInactivityTimer() {
+        if (isEsosInActivityTimerStarted()) {
+            removeMessages(EVENT_ESOS_INACTIVITY_TIMER_TIMED_OUT);
+            plogd("stopEsosInactivityTimer: ESOS inactivity timer stopped");
+        }
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean isEsosInActivityTimerStarted() {
+        return hasMessages(EVENT_ESOS_INACTIVITY_TIMER_TIMED_OUT);
+    }
+
+    private int getP2pSmsInactivityTimeoutDurationSec() {
+        PersistableBundle config = mSatelliteController.getPersistableBundle(getSubId());
+
+        return config.getInt(KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT,
+                DEFAULT_P2P_SMS_INACTIVITY_TIMEOUT_SEC);
+    }
+
+    private void evaluateStartingP2pSmsInactivityTimer() {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("evaluateStartingP2pSmsInactivityTimer: "
+                    + "carrierRoamingNbIotNtn is disabled");
+            return;
+        }
+
+        if (isP2pSmsInActivityTimerStarted()) {
+            plogd("isP2pSmsInActivityTimerStarted: "
+                    + "P2P_SMS inactivity timer already started");
+            return;
+        }
+
+        int subId = getSubId();
+        if (!isP2pSmsSupportedOnCarrierRoamingNtn(subId)) {
+            if (DBG) plogd("evaluateStartingP2pSmsInactivityTimer: P2P_SMS is not supported");
+            return;
+        }
+
+        if (mIsDeviceAlignedWithSatellite) {
+            plogd("evaluateStartingP2pSmsInactivityTimer: "
+                    + "can't start P2P_SMS inactivity timer due to device aligned satellite");
+            return;
+        }
+
+        int timeOutMillis = getP2pSmsInactivityTimeoutDurationSec() * 1000;
+        DatagramController datagramController = DatagramController.getInstance();
+        if (datagramController.isSendingInIdleState()
+                && datagramController.isPollingInIdleState()) {
+            sendMessageDelayed(EVENT_P2P_SMS_INACTIVITY_TIMER_TIMED_OUT, timeOutMillis);
+            plogd("evaluateStartingP2pSmsInactivityTimer: start P2P_SMS inactivity timer "
+                    + timeOutMillis);
+        } else {
+            plogd("evaluateStartingP2pSmsInactivityTimer: "
+                    + "can't start P2P_SMS inactivity timer");
+        }
+    }
+
+    private void stopP2pSmsInactivityTimer() {
+        if (isP2pSmsInActivityTimerStarted()) {
+            removeMessages(EVENT_P2P_SMS_INACTIVITY_TIMER_TIMED_OUT);
+            plogd("stopP2pSmsInactivityTimer: P2P_SMS inactivity timer stopped");
+        }
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean isP2pSmsInActivityTimerStarted() {
+        return hasMessages(EVENT_P2P_SMS_INACTIVITY_TIMER_TIMED_OUT);
+    }
+
+    /**
+     * Initializes the inactivity start timestamp.
+     *
+     * <p>This method is called when 1) the datagram transfer state changes to idle or 2) the
+     * device is unaligned with the satellite.
+     */
+    private void checkForInactivity() {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            return;
+        }
+
+        // If the inactivity start timestamp is not undefined, it means the inactivity has already
+        // started.
+        if (mInactivityStartTimestamp != UNDEFINED_TIMESTAMP) {
+            return;
+        }
+
+        boolean isInactive = mLastDatagramTransferState.isIdle() && !mIsDeviceAlignedWithSatellite;
+        if (isInactive) {
+            mInactivityStartTimestamp = SystemClock.elapsedRealtime();
+        }
+    }
+
+    /**
+     * Updates the max inactivity duration session metric.
+     *
+     * <p>This method is called when 1) the datagram transfer state changes to not idle, 2) the
+     * device is aligned with the satellite, or 3) modem state moves to PowerOffState.
+     */
+    private void endUserInactivity() {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("endUserInactivity: carrierRoamingNbIotNtn is disabled");
+            return;
+        }
+
+        if (mInactivityStartTimestamp != UNDEFINED_TIMESTAMP) {
+            long inactivityDurationMs = SystemClock.elapsedRealtime() - mInactivityStartTimestamp;
+            int inactivityDurationSec = (int) (inactivityDurationMs / 1000);
+            mSessionMetricsStats.updateMaxInactivityDurationSec(inactivityDurationSec);
+
+            mInactivityStartTimestamp = UNDEFINED_TIMESTAMP;
+        }
     }
 
     private void handleEventScreenOffInactivityTimerTimedOut() {
+        if (mSatelliteController.getRequestIsEmergency()) {
+            loge("handleEventScreenOffInactivityTimerTimedOut: Emergency mode");
+            /* This is for coexistence
+             * mIsEmergency can be set after
+             * EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT timer started
+             */
+            return;
+        }
+
         plogd("handleEventScreenOffInactivityTimerTimedOut: request disable satellite");
 
         mSatelliteController.requestSatelliteEnabled(
@@ -1562,7 +1926,6 @@
                         if (result == SATELLITE_RESULT_SUCCESS) {
                             mSessionMetricsStats.addCountOfAutoExitDueToScreenOff();
                         }
-                        // TODO(b/364738085): Add CountOfAutoExitDueToTnNetwork
                     }
                 });
     }
@@ -1645,6 +2008,25 @@
         return true;
     }
 
+    private boolean isP2pSmsSupportedOnCarrierRoamingNtn(int subId) {
+        if (!mSatelliteController.isSatelliteRoamingP2pSmSSupported(subId)) {
+            if (DBG) plogd("isP2pSmsSupportedOnCarrierRoamingNtn: P2P_SMS is not supported");
+            return false;
+        }
+
+        int[] services = mSatelliteController.getSupportedServicesOnCarrierRoamingNtn(subId);
+        if (!ArrayUtils.contains(services, NetworkRegistrationInfo.SERVICE_TYPE_SMS)) {
+            if (DBG) {
+                plogd("isP2pSmsSupportedOnCarrierRoamingNtn: P2P_SMS service is not supported "
+                        + "on carrier roaming ntn.");
+            }
+            return false;
+        }
+
+        if (DBG) plogd("isP2pSmsSupportedOnCarrierRoamingNtn: P2_SMS is supported");
+        return true;
+    }
+
     private boolean isSatellitePersistentLoggingEnabled(
             @NonNull Context context, @NonNull FeatureFlags featureFlags) {
         if (featureFlags.satellitePersistentLogging()) {
@@ -1658,6 +2040,26 @@
         }
     }
 
+    private boolean isConcurrentTnScanningSupported() {
+        try {
+            return mContext.getResources().getBoolean(
+                R.bool.config_satellite_modem_support_concurrent_tn_scanning);
+        } catch (RuntimeException e) {
+            plogd("isConcurrentTnScanningSupported: ex=" + e);
+            return false;
+        }
+    }
+
+    private boolean isTnScanningAllowedDuringSatelliteSession() {
+        try {
+            return mContext.getResources().getBoolean(
+                    R.bool.config_satellite_allow_tn_scanning_during_satellite_session);
+        } catch (RuntimeException e) {
+            plogd("isTnScanningAllowedDuringSatelliteSession: ex=" + e);
+            return false;
+        }
+    }
+
     private void plogd(@NonNull String log) {
         logd(log);
         if (mPersistentLogger != null) {
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java
index 4333253..12de4ac 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java
@@ -47,6 +47,8 @@
     private @SatelliteConstants.ConfigDataSource int mConfigDataSource;
     private int mCarrierId;
     private @SatelliteConstants.TriggeringEvent int mTriggeringEvent;
+    private boolean mIsNtnOnlyCarrier;
+
     private AccessControllerMetricsStats() {
         initializeAccessControllerMetricsParam();
     }
@@ -77,6 +79,7 @@
         mConfigDataSource = CONFIG_DATA_SOURCE_UNKNOWN;
         mCarrierId = UNKNOWN_CARRIER_ID;
         mTriggeringEvent = TRIGGERING_EVENT_UNKNOWN;
+        mIsNtnOnlyCarrier = false;
     }
     /**
      * Sets the Access Control Type for current satellite enablement.
@@ -188,6 +191,16 @@
         return this;
     }
 
+    /**
+     * Sets the value of isNtnOnlyCarrier for current satellite enablement.
+     * @param isNtnOnlyCarrier {@code true} if the carrier is NTN only carrier.
+    */
+    public AccessControllerMetricsStats setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier) {
+        mIsNtnOnlyCarrier = isNtnOnlyCarrier;
+        logd("setIsNtnOnlyCarrier: isNtnOnlyCarrier = " + mIsNtnOnlyCarrier);
+        return this;
+    }
+
     /** Report the access controller metrics atoms to PersistAtomsStorage in telephony. */
     public void reportAccessControllerMetrics() {
         SatelliteStats.SatelliteAccessControllerParams accessControllerParams =
@@ -203,6 +216,7 @@
                         .setConfigDatasource(mConfigDataSource)
                         .setCarrierId(mCarrierId)
                         .setTriggeringEvent(mTriggeringEvent)
+                        .setIsNtnOnlyCarrier(mIsNtnOnlyCarrier)
                         .build();
         logd("reportAccessControllerMetrics: " + accessControllerParams.toString());
         SatelliteStats.getInstance().onSatelliteAccessControllerMetrics(accessControllerParams);
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteSessionStats.java b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteSessionStats.java
index 771432e..3138b16 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteSessionStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteSessionStats.java
@@ -325,13 +325,18 @@
             return;
         }
         String simCountry = MccTable.countryCodeForMcc(subscriptionInfoInternal.getMcc());
-        String satelliteRegisteredCountry = MccTable.countryCodeForMcc(
-                satelliteRegisteredPlmn.substring(0, 3));
-        if (simCountry.equalsIgnoreCase(satelliteRegisteredCountry)) {
-            mIsNtnRoamingInHomeCountry = false;
-        } else {
-            // If device is connected to roaming non-terrestrial network, update to true.
-            mIsNtnRoamingInHomeCountry = true;
+        mIsNtnRoamingInHomeCountry = true;
+        if (satelliteRegisteredPlmn != null
+                && satelliteRegisteredPlmn.length() >= 3) {
+            String satelliteRegisteredCountry = MccTable.countryCodeForMcc(
+                    satelliteRegisteredPlmn.substring(0, 3));
+            if (simCountry.equalsIgnoreCase(satelliteRegisteredCountry)) {
+                mIsNtnRoamingInHomeCountry = true;
+            } else {
+                // If device is connected to roaming non-terrestrial network, then marking as
+                // roaming in external country
+                mIsNtnRoamingInHomeCountry = false;
+            }
         }
         logd("updateNtnRoamingInHomeCountry: mIsNtnRoamingInHomeCountry="
                 + mIsNtnRoamingInHomeCountry);
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
index ec135da..608d91f 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
@@ -35,7 +35,6 @@
 public class ControllerMetricsStats {
     private static final int ADD_COUNT = 1;
     private static final String TAG = ControllerMetricsStats.class.getSimpleName();
-    private static final boolean DBG = false;
 
     private static ControllerMetricsStats sInstance;
 
@@ -112,7 +111,6 @@
         mSatelliteStats = satelliteStats;
     }
 
-
     /** Report a counter when an attempt for satellite service on is successfully done */
     public void reportServiceEnablementSuccessCount() {
         logd("reportServiceEnablementSuccessCount()");
@@ -124,7 +122,7 @@
 
     /** Report a counter when an attempt for satellite service on is failed */
     public void reportServiceEnablementFailCount() {
-        logd("reportServiceEnablementSuccessCount()");
+        logd("reportServiceEnablementFailCount()");
         mSatelliteStats.onSatelliteControllerMetrics(
                 new SatelliteStats.SatelliteControllerParams.Builder()
                         .setCountOfSatelliteServiceEnablementsFail(ADD_COUNT)
@@ -141,14 +139,13 @@
             builder.setCountOfDemoModeOutgoingDatagramSuccess(ADD_COUNT);
         } else {
             builder.setCountOfOutgoingDatagramSuccess(ADD_COUNT);
-        }
-
-        if (SatelliteServiceUtils.isSosMessage(datagramType)) {
-            builder.setCountOfDatagramTypeSosSmsSuccess(ADD_COUNT);
-        } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) {
-            builder.setCountOfDatagramTypeLocationSharingSuccess(ADD_COUNT);
-        } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
-            builder.setCountOfDatagramTypeKeepAliveSuccess(ADD_COUNT).build();
+            if (SatelliteServiceUtils.isSosMessage(datagramType)) {
+                builder.setCountOfDatagramTypeSosSmsSuccess(ADD_COUNT);
+            } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) {
+                builder.setCountOfDatagramTypeLocationSharingSuccess(ADD_COUNT);
+            } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
+                builder.setCountOfDatagramTypeKeepAliveSuccess(ADD_COUNT).build();
+            }
         }
 
         SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
@@ -166,14 +163,13 @@
             builder.setCountOfDemoModeOutgoingDatagramFail(ADD_COUNT);
         } else {
             builder.setCountOfOutgoingDatagramFail(ADD_COUNT);
-        }
-
-        if (SatelliteServiceUtils.isSosMessage(datagramType)) {
-            builder.setCountOfDatagramTypeSosSmsFail(ADD_COUNT);
-        } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) {
-            builder.setCountOfDatagramTypeLocationSharingFail(ADD_COUNT);
-        } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
-            builder.setCountOfDatagramTypeKeepAliveFail(ADD_COUNT);
+            if (SatelliteServiceUtils.isSosMessage(datagramType)) {
+                builder.setCountOfDatagramTypeSosSmsFail(ADD_COUNT);
+            } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) {
+                builder.setCountOfDatagramTypeLocationSharingFail(ADD_COUNT);
+            } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
+                builder.setCountOfDatagramTypeKeepAliveFail(ADD_COUNT);
+            }
         }
 
         SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
@@ -382,7 +378,6 @@
     }
 
     /** Capture the NB-IoT NTN carrier ID */
-    @VisibleForTesting
     public void setCarrierId(int carrierId) {
         logd("setCarrierId:" + carrierId);
         mSatelliteStats.onSatelliteControllerMetrics(
@@ -419,6 +414,33 @@
         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
     }
 
+    /**
+     * Add count when the notification for P2P SMS over satellite avaibility is shown or removed.
+     */
+    public void reportP2PSmsEligibilityNotificationsCount(boolean isEligible) {
+        SatelliteStats.SatelliteControllerParams.Builder builder;
+        if (isEligible) {
+            builder = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfP2PSmsAvailableNotificationShown(ADD_COUNT);
+        } else {
+            builder = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfP2PSmsAvailableNotificationRemoved(ADD_COUNT);
+
+        }
+        SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
+        logd("reportP2PSmsEligibilityNotificationsCount:" + controllerParam);
+        mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
+    }
+
+    /** Capture the latest provisioned state for satellite service */
+    public void setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier) {
+        logd("setIsNtnOnlyCarrier:" + isNtnOnlyCarrier);
+        mSatelliteStats.onSatelliteControllerMetrics(
+                new SatelliteStats.SatelliteControllerParams.Builder()
+                        .setIsNtnOnlyCarrier(isNtnOnlyCarrier)
+                        .build());
+    }
+
     /** Receives the battery status whether it is in charging or not, update interval is 60 sec. */
     private final BroadcastReceiver mBatteryStatusReceiver = new BroadcastReceiver() {
         private long mLastUpdatedTime = 0;
@@ -448,9 +470,7 @@
     }
 
     private static void logd(@NonNull String log) {
-        if (DBG) {
-            Log.d(TAG, log);
-        }
+        Log.d(TAG, log);
     }
 
     private static void loge(@NonNull String log) {
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java
index 73be042..e355078 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java
@@ -29,7 +29,6 @@
  */
 public class ProvisionMetricsStats {
     private static final String TAG = ProvisionMetricsStats.class.getSimpleName();
-    private static final boolean DBG = false;
 
     private static ProvisionMetricsStats sInstance = null;
 
@@ -40,6 +39,7 @@
     private boolean mIsProvisionRequest;
     private boolean mIsCanceled;
     private int mCarrierId;
+    private boolean mIsNtnOnlyCarrier;
 
     private ProvisionMetricsStats() {
         initializeProvisionParams();
@@ -89,6 +89,12 @@
         return this;
     }
 
+    /** Capture the latest provisioned state for satellite service */
+    public ProvisionMetricsStats setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier) {
+        mIsNtnOnlyCarrier = isNtnOnlyCarrier;
+        return this;
+    }
+
     /** Report the provision metrics atoms to PersistAtomsStorage in telephony */
     public void reportProvisionMetrics() {
         SatelliteStats.SatelliteProvisionParams provisionParams =
@@ -99,6 +105,7 @@
                         .setIsProvisionRequest(mIsProvisionRequest)
                         .setIsCanceled(mIsCanceled)
                         .setCarrierId(mCarrierId)
+                        .setIsNtnOnlyCarrier(mIsNtnOnlyCarrier)
                         .build();
         SatelliteStats.getInstance().onSatelliteProvisionMetrics(provisionParams);
         logd("reportProvisionMetrics: " + provisionParams);
@@ -111,11 +118,10 @@
         mIsProvisionRequest = false;
         mIsCanceled = false;
         mCarrierId = UNKNOWN_CARRIER_ID;
+        mIsNtnOnlyCarrier = false;
     }
 
     private static void logd(@NonNull String log) {
-        if (DBG) {
-            Log.d(TAG, log);
-        }
+        Log.d(TAG, log);
     }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java
index a234378..7c3572f 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java
@@ -18,6 +18,7 @@
 
 import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE;
+import static android.telephony.satellite.SatelliteManager.KEY_SESSION_STATS_V2;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
 import android.annotation.NonNull;
@@ -36,7 +37,6 @@
  */
 public class SessionMetricsStats {
     private static final String TAG = SessionMetricsStats.class.getSimpleName();
-    private static final boolean DBG = false;
 
     private static SessionMetricsStats sInstance = null;
     private @SatelliteManager.SatelliteResult int mInitializationResult;
@@ -46,9 +46,13 @@
     private long mTerminationProcessingTimeMillis;
     private int mSessionDurationSec;
     private int mCountOfSuccessfulOutgoingDatagram;
+    private int mShadowCountOfSuccessfulOutgoingDatagram;
     private int mCountOfFailedOutgoingDatagram;
+    private int mShadowCountOfFailedOutgoingDatagram;
     private int mCountOfTimedOutUserMessagesWaitingForConnection;
+    private int mShadowCountOfTimedOutUserMessagesWaitingForConnection;
     private int mCountOfTimedOutUserMessagesWaitingForAck;
+    private int mShadowCountOfTimedOutUserMessagesWaitingForAck;
     private int mCountOfSuccessfulIncomingDatagram;
     private int mCountOfIncomingDatagramFailed;
     private boolean mIsDemoMode;
@@ -57,9 +61,14 @@
     private int mCountOfSatelliteNotificationDisplayed;
     private int mCountOfAutoExitDueToScreenOff;
     private int mCountOfAutoExitDueToTnNetwork;
+    private boolean mIsEmergency;
+    private boolean mIsNtnOnlyCarrier;
+    private int mMaxInactivityDurationSec;
+    private SatelliteSessionStats mDatagramStats;
 
     private SessionMetricsStats() {
         initializeSessionMetricsParam();
+        mDatagramStats = new SatelliteSessionStats();
     }
 
     /**
@@ -124,15 +133,18 @@
 
     /** Increase the count of successful outgoing datagram transmission. */
     public SessionMetricsStats addCountOfSuccessfulOutgoingDatagram(
-            @NonNull @SatelliteManager.DatagramType int datagramType) {
+            @NonNull @SatelliteManager.DatagramType int datagramType,
+            long datagramTransmissionTime) {
+        logd("addCountOfSuccessfulOutgoingDatagram: datagramType=" + datagramType);
+        mDatagramStats.recordSuccessfulOutgoingDatagramStats(datagramType,
+                datagramTransmissionTime);
         if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
             // Ignore KEEP_ALIVE messages
             return this;
         }
 
         mCountOfSuccessfulOutgoingDatagram++;
-        logd("addCountOfSuccessfulOutgoingDatagram: current count="
-                + mCountOfSuccessfulOutgoingDatagram);
+        mShadowCountOfSuccessfulOutgoingDatagram++;
         return this;
     }
 
@@ -140,20 +152,21 @@
     public SessionMetricsStats addCountOfFailedOutgoingDatagram(
             @NonNull @SatelliteManager.DatagramType int datagramType,
             @NonNull @SatelliteManager.SatelliteResult int resultCode) {
+        logd("addCountOfFailedOutgoingDatagram: datagramType=" + datagramType + "  resultCode = "
+                + resultCode);
+        mDatagramStats.addCountOfUnsuccessfulUserMessages(datagramType, resultCode);
         if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
             // Ignore KEEP_ALIVE messages
             return this;
         }
 
         mCountOfFailedOutgoingDatagram++;
-        logd("addCountOfFailedOutgoingDatagram: current count=" + mCountOfFailedOutgoingDatagram);
-
+        mShadowCountOfFailedOutgoingDatagram++;
         if (resultCode == SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE) {
             addCountOfTimedOutUserMessagesWaitingForConnection(datagramType);
         } else if (resultCode == SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT) {
             addCountOfTimedOutUserMessagesWaitingForAck(datagramType);
         }
-
         return this;
     }
 
@@ -166,6 +179,7 @@
         }
 
         mCountOfTimedOutUserMessagesWaitingForConnection++;
+        mShadowCountOfTimedOutUserMessagesWaitingForConnection++;
         logd("addCountOfTimedOutUserMessagesWaitingForConnection: current count="
                 + mCountOfTimedOutUserMessagesWaitingForConnection);
         return this;
@@ -180,6 +194,7 @@
         }
 
         mCountOfTimedOutUserMessagesWaitingForAck++;
+        mShadowCountOfTimedOutUserMessagesWaitingForAck++;
         logd("addCountOfTimedOutUserMessagesWaitingForAck: current count="
                 + mCountOfTimedOutUserMessagesWaitingForAck);
         return this;
@@ -247,6 +262,32 @@
         return this;
     }
 
+    /** Sets whether the session is enabled for emergency or not. */
+    public SessionMetricsStats setIsEmergency(boolean isEmergency) {
+        mIsEmergency = isEmergency;
+        logd("setIsEmergency(" + mIsEmergency + ")");
+        return this;
+    }
+
+    /** Capture the latest provisioned state for satellite service */
+    public SessionMetricsStats setIsNtnOnlyCarrier(boolean isNtnOnlyCarrier) {
+        mIsNtnOnlyCarrier = isNtnOnlyCarrier;
+        logd("setIsNtnOnlyCarrier(" + mIsNtnOnlyCarrier + ")");
+        return this;
+    }
+
+    /** Updates the max inactivity duration session metric. */
+    public SessionMetricsStats updateMaxInactivityDurationSec(int inactivityDurationSec) {
+        if (inactivityDurationSec > mMaxInactivityDurationSec) {
+            mMaxInactivityDurationSec = inactivityDurationSec;
+        }
+        logd("updateMaxInactivityDurationSec: latest inactivty duration (sec)="
+                + inactivityDurationSec
+                + ", max inactivity duration="
+                + mMaxInactivityDurationSec);
+        return this;
+    }
+
     /** Report the session metrics atoms to PersistAtomsStorage in telephony. */
     public void reportSessionMetrics() {
         SatelliteStats.SatelliteSessionParams sessionParams =
@@ -268,6 +309,9 @@
                                 mCountOfSatelliteNotificationDisplayed)
                         .setCountOfAutoExitDueToScreenOff(mCountOfAutoExitDueToScreenOff)
                         .setCountOfAutoExitDueToTnNetwork(mCountOfAutoExitDueToTnNetwork)
+                        .setIsEmergency(mIsEmergency)
+                        .setIsNtnOnlyCarrier(mIsNtnOnlyCarrier)
+                        .setMaxInactivityDurationSec(mMaxInactivityDurationSec)
                         .build();
         logd("reportSessionMetrics: " + sessionParams.toString());
         SatelliteStats.getInstance().onSatelliteSessionMetrics(sessionParams);
@@ -276,18 +320,26 @@
 
     /** Returns {@link SatelliteSessionStats} of the satellite service. */
     public void requestSatelliteSessionStats(int subId, @NonNull ResultReceiver result) {
+        Log.i(TAG, "requestSatelliteSessionStats called");
         Bundle bundle = new Bundle();
         SatelliteSessionStats sessionStats = new SatelliteSessionStats.Builder()
-                .setCountOfSuccessfulUserMessages(mCountOfSuccessfulOutgoingDatagram)
-                .setCountOfUnsuccessfulUserMessages(mCountOfFailedOutgoingDatagram)
+                .setCountOfSuccessfulUserMessages(mShadowCountOfSuccessfulOutgoingDatagram)
+                .setCountOfUnsuccessfulUserMessages(mShadowCountOfFailedOutgoingDatagram)
                 .setCountOfTimedOutUserMessagesWaitingForConnection(
-                        mCountOfTimedOutUserMessagesWaitingForConnection)
+                        mShadowCountOfTimedOutUserMessagesWaitingForConnection)
                 .setCountOfTimedOutUserMessagesWaitingForAck(
-                        mCountOfTimedOutUserMessagesWaitingForAck)
+                        mShadowCountOfTimedOutUserMessagesWaitingForAck)
                 .setCountOfUserMessagesInQueueToBeSent(
                         DatagramDispatcher.getInstance().getPendingUserMessagesCount())
                 .build();
         bundle.putParcelable(SatelliteManager.KEY_SESSION_STATS, sessionStats);
+
+        // Reset countOfUserMessagesInQueueToBeSent for each datagramType to 0.
+        mDatagramStats.resetCountOfUserMessagesInQueueToBeSent();
+
+        DatagramDispatcher.getInstance().updateSessionStatsWithPendingUserMsgCount(mDatagramStats);
+        bundle.putParcelable(KEY_SESSION_STATS_V2, mDatagramStats);
+        Log.i(TAG, "[END] DatagramStats = " + mDatagramStats);
         result.send(SATELLITE_RESULT_SUCCESS, bundle);
     }
 
@@ -302,9 +354,9 @@
     }
 
     private void initializeSessionMetricsParam() {
-        mInitializationResult = SatelliteManager.SATELLITE_RESULT_SUCCESS;
+        mInitializationResult = SATELLITE_RESULT_SUCCESS;
         mRadioTechnology = SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN;
-        mTerminationResult = SatelliteManager.SATELLITE_RESULT_SUCCESS;
+        mTerminationResult = SATELLITE_RESULT_SUCCESS;
         mInitializationProcessingTimeMillis = 0;
         mTerminationProcessingTimeMillis = 0;
         mSessionDurationSec = 0;
@@ -320,12 +372,22 @@
         mCountOfSatelliteNotificationDisplayed = 0;
         mCountOfAutoExitDueToScreenOff = 0;
         mCountOfAutoExitDueToTnNetwork = 0;
+        mIsEmergency = false;
+        mIsNtnOnlyCarrier = false;
+        mMaxInactivityDurationSec = 0;
+    }
+
+    public void resetSessionStatsShadowCounters() {
+        logd("resetTheStatsCounters");
+        mShadowCountOfSuccessfulOutgoingDatagram = 0;
+        mShadowCountOfFailedOutgoingDatagram = 0;
+        mShadowCountOfTimedOutUserMessagesWaitingForConnection = 0;
+        mShadowCountOfTimedOutUserMessagesWaitingForAck = 0;
+        mDatagramStats.clear();
     }
 
     private static void logd(@NonNull String log) {
-        if (DBG) {
-            Log.d(TAG, log);
-        }
+        Log.d(TAG, log);
     }
 
     private static void loge(@NonNull String log) {
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
index d835f2d..9d62972 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
@@ -2121,9 +2121,6 @@
      * @param capabilities Service capabilities bitmasks
      */
     public void setServiceCapabilities(int subId, int capabilities) {
-        if (!mFeatureFlags.dataOnlyCellularService()) {
-            return;
-        }
         writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_SERVICE_CAPABILITIES,
                 capabilities, SubscriptionInfoInternal.Builder::setServiceCapabilities);
     }
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
index 353493b..fc8d607 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
@@ -27,6 +27,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.app.compat.CompatChanges;
@@ -720,7 +721,7 @@
                     return false;
                 }
             } else {
-                if (!mSubscriptionManager.canManageSubscription(subInfo.toSubscriptionInfo(),
+                if (!canManageSubscription(subInfo.toSubscriptionInfo(),
                         callingPackage)) {
                     loge("checkCarrierPrivilegeOnSubList: cannot manage sub " + subId);
                     return false;
@@ -1450,8 +1451,8 @@
             SatelliteController satelliteController = SatelliteController.getInstance();
             boolean isSatelliteEnabledOrBeingEnabled = false;
             if (satelliteController != null) {
-                isSatelliteEnabledOrBeingEnabled = satelliteController.isSatelliteEnabled()
-                        || satelliteController.isSatelliteBeingEnabled();
+                isSatelliteEnabledOrBeingEnabled =
+                        satelliteController.isSatelliteEnabledOrBeingEnabled();
             }
 
             if (!isSatelliteEnabledOrBeingEnabled) {
@@ -1838,37 +1839,35 @@
                     + " newSetting=" + SubscriptionManager.usageSettingToString(newUsageSetting));
         }
 
-        if (mFeatureFlags.dataOnlyCellularService()) {
-            final int[] servicesFromCarrierConfig =
-                    config.getIntArray(
-                            CarrierConfigManager.KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY);
-            int serviceBitmasks = 0;
-            boolean allServicesAreValid = true;
-            // Check if all services from carrier config are valid before setting to db
-            if (servicesFromCarrierConfig == null) {
-                allServicesAreValid = false;
-            } else {
-                for (int service : servicesFromCarrierConfig) {
-                    if (service < SubscriptionManager.SERVICE_CAPABILITY_VOICE
-                            || service > SubscriptionManager.SERVICE_CAPABILITY_MAX) {
-                        allServicesAreValid = false;
-                        break;
-                    } else {
-                        serviceBitmasks |= SubscriptionManager.serviceCapabilityToBitmask(service);
-                    }
+        final int[] servicesFromCarrierConfig =
+                config.getIntArray(
+                        CarrierConfigManager.KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY);
+        int serviceBitmasks = 0;
+        boolean allServicesAreValid = true;
+        // Check if all services from carrier config are valid before setting to db
+        if (servicesFromCarrierConfig == null) {
+            allServicesAreValid = false;
+        } else {
+            for (int service : servicesFromCarrierConfig) {
+                if (service < SubscriptionManager.SERVICE_CAPABILITY_VOICE
+                        || service > SubscriptionManager.SERVICE_CAPABILITY_MAX) {
+                    allServicesAreValid = false;
+                    break;
+                } else {
+                    serviceBitmasks |= SubscriptionManager.serviceCapabilityToBitmask(service);
                 }
             }
-            // In case we get invalid service override, fall back to default value.
-            // DO NOT throw exception which will crash phone process.
-            if (!allServicesAreValid) {
-                serviceBitmasks = SubscriptionManager.getAllServiceCapabilityBitmasks();
-            }
+        }
+        // In case we get invalid service override, fall back to default value.
+        // DO NOT throw exception which will crash phone process.
+        if (!allServicesAreValid) {
+            serviceBitmasks = SubscriptionManager.getAllServiceCapabilityBitmasks();
+        }
 
-            if (serviceBitmasks != subInfo.getServiceCapabilities()) {
-                log("updateSubscriptionByCarrierConfig: serviceCapabilities updated from "
-                        + subInfo.getServiceCapabilities() + " to " + serviceBitmasks);
-                mSubscriptionDatabaseManager.setServiceCapabilities(subId, serviceBitmasks);
-            }
+        if (serviceBitmasks != subInfo.getServiceCapabilities()) {
+            log("updateSubscriptionByCarrierConfig: serviceCapabilities updated from "
+                    + subInfo.getServiceCapabilities() + " to " + serviceBitmasks);
+            mSubscriptionDatabaseManager.setServiceCapabilities(subId, serviceBitmasks);
         }
     }
 
@@ -2268,7 +2267,7 @@
         return getSubscriptionInfoStreamAsUser(BINDER_WRAPPER.getCallingUserHandle())
                 .map(SubscriptionInfoInternal::toSubscriptionInfo)
                 .filter(subInfo -> subInfo.isEmbedded()
-                        && mSubscriptionManager.canManageSubscription(subInfo, callingPackage))
+                        && canManageSubscription(subInfo, callingPackage))
                 .sorted(Comparator.comparing(SubscriptionInfo::getSimSlotIndex)
                         .thenComparing(SubscriptionInfo::getSubscriptionId))
                 .collect(Collectors.toList());
@@ -2994,7 +2993,7 @@
         return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
                 .map(SubscriptionInfoInternal::toSubscriptionInfo)
                 .filter(info -> groupUuid.equals(info.getGroupUuid())
-                        && (mSubscriptionManager.canManageSubscription(info, callingPackage)
+                        && (canManageSubscription(info, callingPackage)
                         || TelephonyPermissions.checkCallingOrSelfReadPhoneStateNoThrow(
                                 mContext, info.getSubscriptionId(), callingPackage,
                         callingFeatureId, "getSubscriptionsInGroup")))
@@ -4722,6 +4721,9 @@
     public boolean isSatelliteProvisionedForNonIpDatagram(int subId) {
         SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager.getSubscriptionInfoInternal(
                 subId);
+        if (subInfo == null) {
+            return false;
+        }
 
         return subInfo.getIsSatelliteProvisionedForNonIpDatagram() == 1;
     }
@@ -4903,6 +4905,15 @@
         return cardNumber;
     }
 
+    private boolean canManageSubscription(SubscriptionInfo subInfo, String packageName) {
+        if (Flags.hsumPackageManager() && UserManager.isHeadlessSystemUserMode()) {
+            return mSubscriptionManager.canManageSubscriptionAsUser(subInfo, packageName,
+                    UserHandle.of(ActivityManager.getCurrentUser()));
+        } else {
+            return mSubscriptionManager.canManageSubscription(subInfo, packageName);
+        }
+    }
+
     /**
      * Log debug messages.
      *
diff --git a/src/java/com/android/internal/telephony/uicc/IccRecords.java b/src/java/com/android/internal/telephony/uicc/IccRecords.java
index 57ea9b9..cccbf19 100644
--- a/src/java/com/android/internal/telephony/uicc/IccRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IccRecords.java
@@ -36,6 +36,7 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.MccTable;
 import com.android.internal.telephony.gsm.SimTlv;
@@ -1300,10 +1301,18 @@
                 return null;
             }
 
+            if (rsp.exception instanceof CommandException commandException) {
+                switch (commandException.getCommandError()) {
+                    case REQUEST_NOT_SUPPORTED:
+                        throw new UnsupportedOperationException(commandException);
+                    default:
+                        // handle other exceptions in the rsp.exception conditional below
+                }
+            }
             if (rsp.exception != null) {
                 loge("getIccSimChallengeResponse exception: " + rsp.exception);
                 //TODO: propagate better exceptions up to the user now that we have them available
-                //in the call stack.
+                //in the call stack (see CommandException switch above).
                 return null;
             }
 
diff --git a/src/java/com/android/internal/telephony/uicc/SimTypeInfo.java b/src/java/com/android/internal/telephony/uicc/SimTypeInfo.java
new file mode 100644
index 0000000..b19e42a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/uicc/SimTypeInfo.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 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.uicc;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class contains the sim type information of active physical slot ids.
+ */
+public class SimTypeInfo {
+
+    /**
+     * SimType (bit mask)
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            SimType.SIM_TYPE_UNKNOWN,
+            SimType.SIM_TYPE_PHYSICAL,
+            SimType.SIM_TYPE_ESIM,
+    })
+    public @interface SimType {
+        /** Unknown SIM */
+        int SIM_TYPE_UNKNOWN = 0;
+        /** Physical SIM (Can have eUICC capabilities) */
+        int SIM_TYPE_PHYSICAL = 1 << 0;
+        /** Embedded SIM*/
+        int SIM_TYPE_ESIM = 1 << 1;
+    }
+
+    public @SimType int mCurrentSimType = SimType.SIM_TYPE_UNKNOWN;
+    // Bitmask of the supported {@code SimType}s
+    public int mSupportedSimTypes;
+
+    /**
+     * Set the current SimType according to the input type.
+     */
+    public void setCurrentSimType(int simType) {
+        switch(simType) {
+            case android.hardware.radio.config.SimType.UNKNOWN:
+                mCurrentSimType = SimType.SIM_TYPE_UNKNOWN;
+                break;
+            case android.hardware.radio.config.SimType.PHYSICAL:
+                mCurrentSimType = SimType.SIM_TYPE_PHYSICAL;
+                break;
+            case android.hardware.radio.config.SimType.ESIM:
+                mCurrentSimType = SimType.SIM_TYPE_ESIM;
+                break;
+            default:
+                throw new RuntimeException("Unrecognized RIL_SimType: " + simType);
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("SimTypeInfo {activeSimType=").append(mCurrentSimType).append(",")
+                .append("supportedSimType=").append(mSupportedSimTypes);
+        sb.append("}");
+        return sb.toString();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
index dd71c44..9db25b6 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccController.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -80,6 +80,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.UUID;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -162,8 +163,7 @@
     // this needs to be here, because on bootup we dont know which index maps to which UiccSlot
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private CommandsInterface[] mCis;
-    @VisibleForTesting
-    public UiccSlot[] mUiccSlots;
+    private UiccSlot[] mUiccSlots;
     private int[] mPhoneIdToSlotId;
     private boolean mIsSlotStatusSupported = true;
 
@@ -491,6 +491,27 @@
     }
 
     /**
+     * Set UiccSlot object for a specific physical slot index on the device.
+     *
+     * This is only supposed to be used internally and by unit tests.
+     *
+     * @param slotId Slot index
+     * @param slot Slot object
+     */
+    @VisibleForTesting
+    public void setUiccSlot(int slotId, @NonNull UiccSlot slot) {
+        synchronized (mLock) {
+            if (!isValidSlotIndex(slotId)) {
+                throw new ArrayIndexOutOfBoundsException("Invalid slot index: " + slotId);
+            }
+            if (mUiccSlots[slotId] != null) {
+                mUiccSlots[slotId].dispose();
+            }
+            mUiccSlots[slotId] = Objects.requireNonNull(slot);
+        }
+    }
+
+    /**
      * API to get UiccSlot object for a given phone id
      * @return UiccSlot object for the given phone id
      */
@@ -1076,7 +1097,7 @@
                 log("Creating mUiccSlots[" + slotId + "]; mUiccSlots.length = "
                         + mUiccSlots.length);
             }
-            mUiccSlots[slotId] = new UiccSlot(mContext, true);
+            setUiccSlot(slotId, new UiccSlot(mContext, true));
         }
 
         mUiccSlots[slotId].update(mCis[index], status, index, slotId);
@@ -1353,7 +1374,7 @@
                 if (VDBG) {
                     log("Creating mUiccSlot[" + i + "]; mUiccSlots.length = " + mUiccSlots.length);
                 }
-                mUiccSlots[i] = new UiccSlot(mContext, isActive);
+                setUiccSlot(i, new UiccSlot(mContext, isActive));
             }
 
             if (isActive) { // check isActive flag so that we don't have to iterate through all
@@ -1803,6 +1824,17 @@
         return mCardStrings;
     }
 
+    /**
+     * Release resources. Must be called each time this class is used.
+     */
+    @VisibleForTesting
+    public void dispose() {
+        for (var slot : mUiccSlots) {
+            slot.dispose();
+        }
+        mUiccSlots = null;
+    }
+
     public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
         IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
         pw.println("mIsCdmaSupported=" + isCdmaSupported(mContext));
diff --git a/src/java/com/android/internal/telephony/uicc/UiccPort.java b/src/java/com/android/internal/telephony/uicc/UiccPort.java
index 905db70..8118a12 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccPort.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccPort.java
@@ -33,6 +33,8 @@
 import com.android.internal.telephony.flags.FeatureFlagsImpl;
 import com.android.telephony.Rlog;
 
+import dalvik.system.CloseGuard;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -46,6 +48,7 @@
     // The lock object is created by UiccSlot that owns this UiccCard - this is to share the lock
     // between UiccSlot, UiccCard, EuiccCard, UiccPort, EuiccPort and UiccProfile for now.
     protected final Object mLock;
+    private final CloseGuard mCloseGuard = CloseGuard.get();
 
     private String mIccid;
     protected String mCardId;
@@ -68,6 +71,7 @@
         if (DBG) log("Creating");
         mPhoneId = phoneId;
         mLock = lock;
+        mCloseGuard.open("cleanup");
         update(c, ci, ics, uiccCard);
     }
 
@@ -97,6 +101,7 @@
     public void dispose() {
         synchronized (mLock) {
             if (DBG) log("Disposing Port");
+            mCloseGuard.close();
             if (mUiccProfile != null) {
                 mUiccProfile.dispose();
             }
@@ -106,9 +111,14 @@
     }
 
     @Override
-    protected void finalize() {
+    protected void finalize() throws Throwable {
         if (DBG) log("UiccPort finalized");
-        cleanupOpenLogicalChannelRecordsIfNeeded();
+        try {
+            if (mCloseGuard != null) mCloseGuard.warnIfOpen();
+            cleanupOpenLogicalChannelRecordsIfNeeded();
+        } finally {
+            super.finalize();
+        }
     }
 
     /**
@@ -440,7 +450,13 @@
      * removal or modem reset. The obsoleted records may trigger a redundant release of logical
      * channel that may have been assigned to other client.
      */
+    @SuppressWarnings("GuardedBy")
     private void cleanupOpenLogicalChannelRecordsIfNeeded() {
+        // This check may raise GuardedBy warning, but we need it as long as this method is called
+        // from finalize(). We can remove it from there once UiccPort is fully protected against
+        // resource leak (e.g. with CloseGuard) and all (direct and indirect) users are fixed.
+        if (mOpenChannelRecords == null) return;
+
         synchronized (mOpenChannelRecords) {
             for (OpenLogicalChannelRecord record : mOpenChannelRecords) {
                 if (DBG) log("Clean up " + record);
diff --git a/src/java/com/android/internal/telephony/uicc/UiccSlot.java b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
index db10271..d986c93 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccSlot.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccSlot.java
@@ -391,6 +391,13 @@
         }
     }
 
+    /**
+     * Release resources. Must be called each time this class is used.
+     */
+    public void dispose() {
+        nullifyUiccCard(false);
+    }
+
     public boolean isStateUnknown() {
         // CardState is not specific to any port index, use default port.
         CardState cardState = mCardState.get(TelephonyManager.DEFAULT_PORT_INDEX);
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java b/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java
index 3bd66f8..7bdec47 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java
@@ -133,7 +133,8 @@
             UiccCard card, MultipleEnabledProfilesMode supportedMepMode) {
         super(c, ci, ics, phoneId, lock, card);
         // TODO: Set supportExtendedApdu based on ATR.
-        mApduSender = new ApduSender(ci, ISD_R_AID, false /* supportExtendedApdu */);
+        mApduSender = new ApduSender(c, phoneId, ci, ISD_R_AID,
+                              false /* supportExtendedApdu */);
         if (TextUtils.isEmpty(ics.eid)) {
             loge("no eid given in constructor for phone " + phoneId);
         } else {
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java b/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
index 0a56b2b..309fe79 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
@@ -17,11 +17,17 @@
 package com.android.internal.telephony.uicc.euicc.apdu;
 
 import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
+import android.preference.PreferenceManager;
 import android.telephony.IccOpenLogicalChannelResponse;
+import android.util.Base64;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.euicc.EuiccSession;
 import com.android.internal.telephony.uicc.IccIoResult;
 import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback;
 import com.android.internal.telephony.uicc.euicc.async.AsyncResultHelper;
@@ -36,7 +42,14 @@
  * before sending and closed after all APDU commands are sent. The complete response of the last
  * APDU command will be returned. If any APDU command returns an error status (other than
  * {@link #STATUS_NO_ERROR}) or causing an exception, an {@link ApduException} will be returned
- * immediately without sending the rest of commands. This class is thread-safe.
+ * immediately without sending the rest of commands.
+ *
+ * <p>If {@link EuiccSession} indicates ongoing session(s), the behavior changes: 1) before
+ * sending, check if a channel is opened already. If yes, reuse the channel and send APDU commands
+ * directly. If no, open a channel before sending. 2) The channel is closed when EuiccSession
+ * class ends all sessions, independent of APDU sending.
+ *
+ * <p>This class is thread-safe.
  *
  * @hide
  */
@@ -50,8 +63,12 @@
     // Status code of APDU response
     private static final int STATUS_NO_ERROR = 0x9000;
     private static final int SW1_NO_ERROR = 0x91;
+    private static final int STATUS_CHANNEL_CLOSED = 0x6881; // b/359336875
 
     private static final int WAIT_TIME_MS = 2000;
+    private static final String CHANNEL_ID_PRE = "esim-channel";
+    static final String ISD_R_AID = "A0000005591010FFFFFFFF8900000100";
+    private static final String CHANNEL_RESPONSE_ID_PRE = "esim-res-id";
 
     private static void logv(String msg) {
         Rlog.v(LOG_TAG, msg);
@@ -61,26 +78,48 @@
         Rlog.d(LOG_TAG, msg);
     }
 
+    private static void loge(String msg) {
+        Rlog.e(LOG_TAG, msg);
+    }
+
     private final String mAid;
     private final boolean mSupportExtendedApdu;
     private final OpenLogicalChannelInvocation mOpenChannel;
     private final CloseLogicalChannelInvocation mCloseChannel;
     private final TransmitApduLogicalChannelInvocation mTransmitApdu;
+    private final Context mContext;
+    private final String mChannelKey;
+    private final String mChannelResponseKey;
+    // closeAnyOpenChannel() needs a handler for its async callbacks.
+    private final Handler mHandler;
 
-    // Lock for accessing mChannelOpened. We only allow to open a single logical channel at any
-    // time for an AID.
-    private final Object mChannelLock = new Object();
+    // Lock for accessing mChannelInUse. We only allow to open a single logical
+    // channel at any time for an AID and to invoke one command at any time.
+    // Only the thread (and its async callbacks) that sets mChannelInUse
+    // can open/close/send, and update mChannelOpened.
+    private final Object mChannelInUseLock = new Object();
+    @GuardedBy("mChannelInUseLock")
+    private boolean mChannelInUse;
     private boolean mChannelOpened;
 
     /**
      * @param aid The AID that will be used to open a logical channel to.
      */
-    public ApduSender(CommandsInterface ci, String aid, boolean supportExtendedApdu) {
+    public ApduSender(Context context, int phoneId, CommandsInterface ci, String aid,
+            boolean supportExtendedApdu) {
+        if (!aid.equals(ISD_R_AID) && !"user".equals(Build.TYPE)) {
+            throw new IllegalArgumentException("Only ISD-R AID is supported.");
+        }
         mAid = aid;
+        mContext = context;
         mSupportExtendedApdu = supportExtendedApdu;
         mOpenChannel = new OpenLogicalChannelInvocation(ci);
         mCloseChannel = new CloseLogicalChannelInvocation(ci);
         mTransmitApdu = new TransmitApduLogicalChannelInvocation(ci);
+        mChannelKey = CHANNEL_ID_PRE + "_" + phoneId;
+        mChannelResponseKey = CHANNEL_RESPONSE_ID_PRE + "_" + phoneId;
+        mHandler = new Handler();
+        mChannelInUse = false;
     }
 
     /**
@@ -99,79 +138,133 @@
             RequestProvider requestProvider,
             ApduSenderResultCallback resultCallback,
             Handler handler) {
-        synchronized (mChannelLock) {
-            if (mChannelOpened) {
-                if (!Looper.getMainLooper().equals(Looper.myLooper())) {
-                    logd("Logical channel has already been opened. Wait.");
-                    try {
-                        mChannelLock.wait(WAIT_TIME_MS);
-                    } catch (InterruptedException e) {
-                        // nothing to do
-                    }
-                    if (mChannelOpened) {
-                        AsyncResultHelper.throwException(
-                                new ApduException("The logical channel is still in use."),
-                                resultCallback, handler);
-                        return;
-                    }
-                } else {
-                    AsyncResultHelper.throwException(
-                            new ApduException("The logical channel is in use."),
-                            resultCallback, handler);
-                    return;
-                }
-            }
-            mChannelOpened = true;
+        if (!acquireChannelLock()) {
+            AsyncResultHelper.throwException(
+                    new ApduException("The logical channel is still in use."),
+                    resultCallback,
+                    handler);
+            return;
         }
 
-        mOpenChannel.invoke(mAid, new AsyncResultCallback<IccOpenLogicalChannelResponse>() {
-            @Override
-            public void onResult(IccOpenLogicalChannelResponse openChannelResponse) {
-                int channel = openChannelResponse.getChannel();
-                int status = openChannelResponse.getStatus();
-                if (channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL
-                        || status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR) {
-                    synchronized (mChannelLock) {
-                        mChannelOpened = false;
-                        mChannelLock.notify();
-                    }
-                    resultCallback.onException(
-                            new ApduException("Failed to open logical channel opened for AID: "
-                                    + mAid + ", with status: " + status));
-                    return;
-                }
-
-                RequestBuilder builder = new RequestBuilder(channel, mSupportExtendedApdu);
-                Throwable requestException = null;
-                try {
-                    requestProvider.buildRequest(openChannelResponse.getSelectResponse(), builder);
-                } catch (Throwable e) {
-                    requestException = e;
-                }
-                if (builder.getCommands().isEmpty() || requestException != null) {
-                    // Just close the channel if we don't have commands to send or an error
-                    // was encountered.
-                    closeAndReturn(channel, null /* response */, requestException, resultCallback,
-                            handler);
-                    return;
-                }
-                sendCommand(builder.getCommands(), 0 /* index */, resultCallback, handler);
+        boolean euiccSession = EuiccSession.get().hasSession();
+        // Case 1, channel was already opened AND EuiccSession is ongoing.
+        // sendCommand directly. Do not immediately close channel after sendCommand.
+        // Case 2, channel was already opened AND EuiccSession is not ongoing. This means
+        // EuiccSession#endSession is already called but closeAnyOpenChannel() is not
+        // yet executed because of waiting to acquire lock hold by this thread.
+        // sendCommand directly. Close channel immediately anyways after sendCommand.
+        // Case 3, channel is not open AND EuiccSession is ongoing. Open channel
+        // before sendCommand. Do not immediately close channel after sendCommand.
+        // Case 4, channel is not open AND EuiccSession is not ongoing. Open channel
+        // before sendCommand. Close channel immediately after sendCommand.
+        if (mChannelOpened) {  // Case 1 or 2
+            if (euiccSession) {
+                EuiccSession.get().noteChannelOpen(this);
             }
-        }, handler);
+            RequestBuilder builder = getRequestBuilderWithOpenedChannel(requestProvider,
+                    !euiccSession /* closeChannelImmediately */, resultCallback, handler);
+            if (builder == null) {
+                return;
+            }
+            sendCommand(builder.getCommands(), 0 /* index */,
+                    !euiccSession /* closeChannelImmediately */, resultCallback, handler);
+        } else {  // Case 3 or 4
+            if (euiccSession) {
+                EuiccSession.get().noteChannelOpen(this);
+            }
+            openChannel(requestProvider,
+                    !euiccSession /* closeChannelImmediately */, resultCallback, handler);
+        }
     }
 
-    /**
-     * Closes any open channel.
-     *
-     * <p>Used by EuiccSession#endSession.
-     */
-    public void closeAnyOpenChannel() {
-        // TODO: implement this. Different from existing closeExistingChannelIfExists()
-        // which is only used in constructor and don't worry about multi-thread racing.
-        // 1. Acquire channel lock
-        // 2. Check sharedpref for existing open channel
-        // 3. Close any open channel
-        // 4. Release channel lock
+    private RequestBuilder getRequestBuilderWithOpenedChannel(
+            RequestProvider requestProvider,
+            boolean closeChannelImmediately,
+            ApduSenderResultCallback resultCallback,
+            Handler handler) {
+        Throwable requestException = null;
+        int channel =
+                PreferenceManager.getDefaultSharedPreferences(mContext)
+                        .getInt(mChannelKey, IccOpenLogicalChannelResponse.INVALID_CHANNEL);
+        String storedResponse =
+                PreferenceManager.getDefaultSharedPreferences(mContext)
+                        .getString(mChannelResponseKey, "");
+        byte[] selectResponse = Base64.decode(storedResponse, Base64.DEFAULT);
+        RequestBuilder builder = new RequestBuilder(channel, mSupportExtendedApdu);
+        try {
+            requestProvider.buildRequest(selectResponse, builder);
+        } catch (Throwable e) {
+            requestException = e;
+        }
+        if (builder.getCommands().isEmpty() || requestException != null) {
+            logd("Release as commands are empty or exception occurred");
+            returnRespnseOrException(channel, closeChannelImmediately,
+                    null /* response */, requestException, resultCallback, handler);
+            return null;
+        }
+        return builder;
+    }
+
+    private void openChannel(
+            RequestProvider requestProvider,
+            boolean closeChannelImmediately,
+            ApduSenderResultCallback resultCallback,
+            Handler handler) {
+        mOpenChannel.invoke(mAid, new AsyncResultCallback<IccOpenLogicalChannelResponse>() {
+                    @Override
+                    public void onResult(IccOpenLogicalChannelResponse openChannelResponse) {
+                        int channel = openChannelResponse.getChannel();
+                        int status = openChannelResponse.getStatus();
+                        byte[] selectResponse = openChannelResponse.getSelectResponse();
+                        if (status == IccOpenLogicalChannelResponse.STATUS_NO_SUCH_ELEMENT) {
+                            channel = PreferenceManager.getDefaultSharedPreferences(mContext)
+                                            .getInt(mChannelKey,
+                                                    IccOpenLogicalChannelResponse.INVALID_CHANNEL);
+                            if (channel != IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
+                                logv("Try to use already opened channel: " + channel);
+                                status = IccOpenLogicalChannelResponse.STATUS_NO_ERROR;
+                                String storedResponse = PreferenceManager
+                                        .getDefaultSharedPreferences(mContext)
+                                              .getString(mChannelResponseKey, "");
+                                selectResponse = Base64.decode(storedResponse, Base64.DEFAULT);
+                            }
+                        }
+
+                        if (channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL
+                                || status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR) {
+                            mChannelOpened = false;
+                            returnRespnseOrException(
+                                    channel,
+                                    /* closeChannelImmediately= */ false,
+                                    /* response= */ null,
+                                    new ApduException(
+                                            "Failed to open logical channel for AID: "
+                                                    + mAid
+                                                    + ", with status: "
+                                                    + status),
+                                    resultCallback,
+                                    handler);
+                            return;
+                        }
+                        PreferenceManager.getDefaultSharedPreferences(mContext)
+                                .edit()
+                                .putInt(mChannelKey, channel)
+                                .putString(mChannelResponseKey,
+                                    Base64.encodeToString(selectResponse, Base64.DEFAULT)).apply();
+                        mChannelOpened = true;
+
+                        RequestBuilder builder =
+                                getRequestBuilderWithOpenedChannel(requestProvider,
+                                        closeChannelImmediately, resultCallback, handler);
+                        if (builder == null) {
+                            return;
+                        }
+
+                        sendCommand(builder.getCommands(), 0 /* index */,
+                                closeChannelImmediately, resultCallback, handler);
+                    }
+                },
+                handler);
     }
 
     /**
@@ -184,6 +277,7 @@
     private void sendCommand(
             List<ApduCommand> commands,
             int index,
+            boolean closeChannelImmediately,
             ApduSenderResultCallback resultCallback,
             Handler handler) {
         ApduCommand command = commands.get(index);
@@ -198,9 +292,21 @@
                             public void onResult(IccIoResult fullResponse) {
                                 logv("Full APDU response: " + fullResponse);
                                 int status = (fullResponse.sw1 << 8) | fullResponse.sw2;
-                                if (status != STATUS_NO_ERROR && fullResponse.sw1 != SW1_NO_ERROR) {
-                                    closeAndReturn(command.channel, null /* response */,
-                                            new ApduException(status), resultCallback, handler);
+                                if (status != STATUS_NO_ERROR
+                                        && fullResponse.sw1 != SW1_NO_ERROR) {
+                                    if (status == STATUS_CHANNEL_CLOSED) {
+                                        // Channel is closed by EUICC e.g. REFRESH.
+                                        tearDownPreferences();
+                                        mChannelOpened = false;
+                                        // TODO: add retry
+                                    }
+                                    returnRespnseOrException(
+                                            command.channel,
+                                            closeChannelImmediately,
+                                            null /* response */,
+                                            new ApduException(status),
+                                            resultCallback,
+                                            handler);
                                     return;
                                 }
 
@@ -210,11 +316,17 @@
                                                 fullResponse);
                                 if (continueSendCommand) {
                                     // Sends the next command
-                                    sendCommand(commands, index + 1, resultCallback, handler);
+                                    sendCommand(commands, index + 1,
+                                            closeChannelImmediately, resultCallback, handler);
                                 } else {
                                     // Returns the result of the last command
-                                    closeAndReturn(command.channel, fullResponse.payload,
-                                            null /* exception */, resultCallback, handler);
+                                    returnRespnseOrException(
+                                            command.channel,
+                                            closeChannelImmediately,
+                                            fullResponse.payload,
+                                            null /* exception */,
+                                            resultCallback,
+                                            handler);
                                 }
                             }
                         }, handler);
@@ -237,7 +349,7 @@
             AsyncResultCallback<IccIoResult> resultCallback,
             Handler handler) {
         ByteArrayOutputStream resultBuilder =
-                responseBuilder == null ? new ByteArrayOutputStream() : responseBuilder;
+            responseBuilder == null ? new ByteArrayOutputStream() : responseBuilder;
         if (lastResponse.payload != null) {
             try {
                 resultBuilder.write(lastResponse.payload);
@@ -263,6 +375,41 @@
                 }, handler);
     }
 
+    private void tearDownPreferences() {
+        PreferenceManager.getDefaultSharedPreferences(mContext)
+                .edit()
+                .remove(mChannelKey)
+                .remove(mChannelResponseKey)
+                .apply();
+    }
+
+    /**
+     * Fires the {@code resultCallback} to return a response or exception. Also
+     * closes the open logical channel if {@code closeChannelImmediately} is {@code true}.
+     */
+    private void returnRespnseOrException(
+            int channel,
+            boolean closeChannelImmediately,
+            @Nullable byte[] response,
+            @Nullable Throwable exception,
+            ApduSenderResultCallback resultCallback,
+            Handler handler) {
+        if (closeChannelImmediately) {
+            closeAndReturn(
+                    channel,
+                    response,
+                    exception,
+                    resultCallback,
+                    handler);
+        } else {
+            releaseChannelLockAndReturn(
+                    response,
+                    exception,
+                    resultCallback,
+                    handler);
+        }
+    }
+
     /**
      * Closes the opened logical channel.
      *
@@ -280,10 +427,9 @@
         mCloseChannel.invoke(channel, new AsyncResultCallback<Boolean>() {
             @Override
             public void onResult(Boolean aBoolean) {
-                synchronized (mChannelLock) {
-                    mChannelOpened = false;
-                    mChannelLock.notify();
-                }
+                tearDownPreferences();
+                mChannelOpened = false;
+                releaseChannelLock();
 
                 if (exception == null) {
                     resultCallback.onResult(response);
@@ -293,4 +439,99 @@
             }
         }, handler);
     }
+
+    /**
+     * Cleanup the existing opened channel which remained opened earlier due
+     * to:
+     *
+     * <p> 1) onging EuiccSession. This will be called by {@link EuiccSession#endSession()}
+     * from non-main-thread. Or,
+     *
+     * <p> 2) telephony crash. This will be called by constructor from main-thread.
+     */
+    public void closeAnyOpenChannel() {
+        if (!acquireChannelLock()) {
+            // This cannot happen for case 2) when called by constructor
+            loge("[closeAnyOpenChannel] failed to acquire channel lock");
+            return;
+        }
+        int channelId = PreferenceManager.getDefaultSharedPreferences(mContext)
+                .getInt(mChannelKey, IccOpenLogicalChannelResponse.INVALID_CHANNEL);
+        if (channelId == IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
+            releaseChannelLock();
+            return;
+        }
+        logv("[closeAnyOpenChannel] closing the open channel : " +  channelId);
+        mCloseChannel.invoke(channelId, new AsyncResultCallback<Boolean>() {
+            @Override
+            public void onResult(Boolean isSuccess) {
+                if (isSuccess) {
+                    logv("[closeAnyOpenChannel] Channel closed successfully: " + channelId);
+                    tearDownPreferences();
+                }
+                // Even if CloseChannel failed, pretend that the channel is closed.
+                // So next send() will try open the channel again. If the channel is
+                // indeed still open, we use the channelId saved in sharedPref.
+                mChannelOpened = false;
+                releaseChannelLock();
+            }
+        }, mHandler);
+    }
+
+    // releases channel and callback
+    private void releaseChannelLockAndReturn(
+            @Nullable byte[] response,
+            @Nullable Throwable exception,
+            ApduSenderResultCallback resultCallback,
+            Handler handler) {
+        handler.post(
+                () -> {
+                    releaseChannelLock();
+                    if (exception == null) {
+                        resultCallback.onResult(response);
+                    } else {
+                        resultCallback.onException(exception);
+                    }
+                });
+    }
+
+    private void releaseChannelLock() {
+        synchronized (mChannelInUseLock) {
+            logd("Channel lock released.");
+            mChannelInUse = false;
+            mChannelInUseLock.notify();
+        }
+    }
+
+    /**
+     * Acquires channel lock and returns {@code true} if successful.
+     *
+     * <p>It fails and returns {@code false} when:
+     * <ul>
+     *   <li>Called from main thread, and mChannelInUse=true, fails immediately.
+     *   <li>Called from non main thread, and mChannelInUse=true after 2 seconds waiting, fails.
+     * </ul>
+     */
+    private boolean acquireChannelLock() {
+        synchronized (mChannelInUseLock) {
+            if (mChannelInUse) {
+                if (!Looper.getMainLooper().equals(Looper.myLooper())) {
+                    logd("Logical channel is in use. Wait.");
+                    try {
+                        mChannelInUseLock.wait(WAIT_TIME_MS);
+                    } catch (InterruptedException e) {
+                        // nothing to do
+                    }
+                    if (mChannelInUse) {
+                        return false;
+                    }
+                } else {
+                    return false;
+                }
+            }
+            mChannelInUse = true;
+            logd("Channel lock acquired.");
+            return true;
+        }
+    }
 }
diff --git a/tests/telephonytests/src/android/telephony/ims/RcsConfigTest.java b/tests/telephonytests/src/android/telephony/ims/RcsConfigTest.java
index 4889187..c690ab4 100644
--- a/tests/telephonytests/src/android/telephony/ims/RcsConfigTest.java
+++ b/tests/telephonytests/src/android/telephony/ims/RcsConfigTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.telephony.ims;
+package android.telephony.ims;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -36,6 +36,7 @@
 
 import com.android.internal.telephony.FakeTelephonyProvider;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -176,6 +177,11 @@
         createFakeSimInfo();
     }
 
+    @After
+    public void tearDown() {
+        mFakeTelephonyProvider.close();
+    }
+
     @Test
     @SmallTest
     public void testLoadAndUpdateConfigForSub() {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java
index a860dff..1c58ef2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java
@@ -220,8 +220,9 @@
         // Capture CarrierConfigChangeListener to emulate the carrier config change notification
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
-        CarrierPrivilegesTracker cpt = new CarrierPrivilegesTracker(mTestableLooper.getLooper(),
-                mPhone, mContext, mFeatureFlags);
+        CarrierPrivilegesTracker cpt =
+                new CarrierPrivilegesTracker(
+                        mTestableLooper.getLooper(), mPhone, mContext, mFeatureFlags);
         verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
                 listenerArgumentCaptor.capture());
         mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(0);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index 70e3dee..9788320 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -241,6 +241,14 @@
         }
 
         @Override
+        public boolean bindServiceAsUser(
+                Intent serviceIntent,
+                ServiceConnection connection,
+                int flags, UserHandle user) {
+            return bindService(serviceIntent, connection, flags);
+        }
+
+        @Override
         public void unbindService(
                 ServiceConnection connection) {
             IInterface service = mServiceByServiceConnection.remove(connection);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
index 8dad3ec..522cdac 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
@@ -15,6 +15,12 @@
  */
 package com.android.internal.telephony;
 
+import static android.telephony.CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI;
+import static android.telephony.CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST;
+import static android.telephony.SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP;
+import static android.telephony.SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2;
+import static android.telephony.SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.eq;
@@ -26,9 +32,11 @@
 
 import android.telephony.CellIdentityGsm;
 import android.telephony.CellInfo;
+import android.telephony.CellularIdentifierDisclosure;
 import android.telephony.DisconnectCause;
 import android.telephony.PreciseCallState;
 import android.telephony.PreciseDisconnectCause;
+import android.telephony.SecurityAlgorithmUpdate;
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
@@ -378,6 +386,51 @@
 
     @Test
     @SmallTest
+    public void testNotifyCallbackModeStarted() {
+        doReturn(true).when(mFeatureFlags).emergencyCallbackModeNotification();
+        int phoneId = mPhone.getPhoneId();
+        int subId = mPhone.getSubId();
+        int type = 1;
+        long durationMillis = 1000;
+
+        mDefaultPhoneNotifierUT.notifyCallbackModeStarted(mPhone, type, durationMillis);
+
+        verify(mTelephonyRegistryManager).notifyCallbackModeStarted(eq(phoneId), eq(subId),
+                eq(type), eq(durationMillis));
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifyCallbackModeRestarted() {
+        doReturn(true).when(mFeatureFlags).emergencyCallbackModeNotification();
+        int phoneId = mPhone.getPhoneId();
+        int subId = mPhone.getSubId();
+        int type = 1;
+        long durationMillis = 1000;
+
+        mDefaultPhoneNotifierUT.notifyCallbackModeRestarted(mPhone, type, durationMillis);
+
+        verify(mTelephonyRegistryManager).notifyCallbackModeRestarted(eq(phoneId), eq(subId),
+                eq(type), eq(durationMillis));
+    }
+
+    @Test
+    @SmallTest
+    public void testNotifyCallbackModeStopped() {
+        doReturn(true).when(mFeatureFlags).emergencyCallbackModeNotification();
+        int phoneId = mPhone.getPhoneId();
+        int subId = mPhone.getSubId();
+        int type = 1;
+        int reason = 0;
+
+        mDefaultPhoneNotifierUT.notifyCallbackModeStopped(mPhone, type, reason);
+
+        verify(mTelephonyRegistryManager).notifyCallbackModeStopped(eq(phoneId), eq(subId),
+                eq(type), eq(reason));
+    }
+
+    @Test
+    @SmallTest
     public void testCarrierRoamingNtnModeChanged() {
         int subId = mPhone.getSubId();
         mDefaultPhoneNotifierUT.notifyCarrierRoamingNtnModeChanged(mPhone, true);
@@ -393,4 +446,46 @@
         verify(mTelephonyRegistryManager).notifyCarrierRoamingNtnEligibleStateChanged(
                 eq(subId), eq(true));
     }
+
+    @Test
+    @SmallTest
+    public void testCarrierRoamingNtnAvailableServicesChanged() {
+        int subId = mPhone.getSubId();
+        int[] testServices = {3, 6};
+        mDefaultPhoneNotifierUT.notifyCarrierRoamingNtnAvailableServicesChanged(
+                mPhone, testServices);
+        verify(mTelephonyRegistryManager).notifyCarrierRoamingNtnAvailableServicesChanged(
+                eq(subId), eq(testServices));
+    }
+
+    @Test
+    @SmallTest
+    public void testSecurityAlgorithmsChanged() {
+        doReturn(true).when(mFeatureFlags).securityAlgorithmsUpdateIndications();
+        int phoneId = mPhone.getPhoneId();
+        int subId = mPhone.getSubId();
+        SecurityAlgorithmUpdate update =
+                new SecurityAlgorithmUpdate(
+                        CONNECTION_EVENT_VOLTE_SIP, SECURITY_ALGORITHM_EEA2,
+                        SECURITY_ALGORITHM_HMAC_SHA1_96, false);
+        mDefaultPhoneNotifierUT.notifySecurityAlgorithmsChanged(mPhone, update);
+        verify(mTelephonyRegistryManager).notifySecurityAlgorithmsChanged(
+                eq(phoneId), eq(subId), eq(update));
+    }
+
+    @Test
+    @SmallTest
+    public void testCellularIdentifierDisclosedChanged() {
+        doReturn(true).when(mFeatureFlags).cellularIdentifierDisclosureIndications();
+        int phoneId = mPhone.getPhoneId();
+        int subId = mPhone.getSubId();
+        CellularIdentifierDisclosure disclosure =
+                new CellularIdentifierDisclosure(NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                        CELLULAR_IDENTIFIER_IMSI,
+                        "001001",
+                        false);
+        mDefaultPhoneNotifierUT.notifyCellularIdentifierDisclosedChanged(mPhone, disclosure);
+        verify(mTelephonyRegistryManager).notifyCellularIdentifierDisclosedChanged(
+                eq(phoneId), eq(subId), eq(disclosure));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
index c923f69..101c668 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
@@ -196,4 +196,11 @@
                 selectionArgs);
         return count;
     }
+
+    /**
+     * Release resources. Must be called each time this class is used.
+     */
+    public void close() {
+        mDbHelper.close();
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/FdnUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/FdnUtilsTest.java
index 9da19bc..996fa2d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/FdnUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/FdnUtilsTest.java
@@ -181,4 +181,13 @@
 
         assertTrue(FdnUtils.isFDN("1234560000@ims.mnc.org", "US", fdnList));
     }
+
+    @Test
+    public void dialStrInNationalFormat_returnsTrue() {
+        ArrayList<AdnRecord> fdnList = initializeFdnList();
+        AdnRecord adnRecord = new AdnRecord(null, "0469887529");
+        fdnList.add(8, adnRecord);
+
+        assertTrue(FdnUtils.isFDN("0469887529", "US", fdnList));
+    }
 }
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GbaManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/GbaManagerTest.java
index bad32e9..8898a0f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GbaManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GbaManagerTest.java
@@ -41,6 +41,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.telephony.IBootstrapAuthenticationCallback;
 import android.telephony.TelephonyManager;
 import android.telephony.gba.GbaAuthRequest;
@@ -51,8 +52,6 @@
 import android.testing.TestableLooper;
 import android.util.Log;
 
-import androidx.test.filters.SmallTest;
-
 import com.android.internal.telephony.metrics.RcsStats;
 
 import org.junit.After;
@@ -66,7 +65,7 @@
  */
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public final class GbaManagerTest {
+public final class GbaManagerTest extends TelephonyTest {
     private static final String LOG_TAG = "GbaManagerTest";
 
     private static final ComponentName TEST_DEFAULT_SERVICE_NAME = new ComponentName(
@@ -91,6 +90,7 @@
 
     @Before
     public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
         log("setUp");
         mMockContext = mock(Context.class);
         mMockBinder = mock(IBinder.class);
@@ -100,7 +100,8 @@
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
-        when(mMockContext.bindService(any(), any(), anyInt())).thenReturn(true);
+        when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any(UserHandle.class)))
+                .thenReturn(true);
         when(mMockGbaServiceBinder.asBinder()).thenReturn(mMockBinder);
         mTestGbaManager = new GbaManager(mMockContext, TEST_SUB_ID, null, 0, mMockRcsStats);
         mHandler = mTestGbaManager.getHandler();
@@ -109,137 +110,129 @@
         } catch (Exception e) {
             fail("Unable to create looper from handler.");
         }
+        monitorTestableLooper(mLooper);
     }
 
     @After
     public void tearDown() throws Exception {
         log("tearDown");
         mTestGbaManager.destroy();
-        mTestGbaManager = null;
-        mHandler = null;
-        mLooper.destroy();
-        mLooper = null;
+        super.tearDown();
     }
 
     @Test
-    @SmallTest
     public void testFailOnRequest() throws Exception {
         GbaAuthRequest request = createDefaultRequest();
 
         mTestGbaManager.bootstrapAuthenticationRequest(request);
-        mLooper.processAllMessages();
+        processAllMessages();
 
-        verify(mMockContext, never()).bindService(any(), any(), anyInt());
+        verify(mMockContext, never()).bindServiceAsUser(any(), any(), anyInt(),
+                any(UserHandle.class));
         verify(mMockCallback).onAuthenticationFailure(anyInt(), anyInt());
         assertTrue(!mTestGbaManager.isServiceConnected());
     }
 
     @Test
-    @SmallTest
     public void testBindServiceOnRequest() throws Exception {
-        mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName());
+        mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName(), 123);
         GbaAuthRequest request = createDefaultRequest();
 
         mTestGbaManager.bootstrapAuthenticationRequest(request);
-        mLooper.processAllMessages();
+        processAllMessages();
         bindAndConnectService(TEST_DEFAULT_SERVICE_NAME);
-        mLooper.processAllMessages();
+        processAllMessages();
 
         verify(mMockGbaServiceBinder).authenticationRequest(any());
         assertTrue(mTestGbaManager.isServiceConnected());
     }
 
     @Test
-    @SmallTest
     public void testFailAndRetryOnRequest() throws RemoteException {
-        when(mMockContext.bindService(any(), any(), anyInt())).thenReturn(false);
-        mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName());
+        when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any(UserHandle.class)))
+                .thenReturn(false);
+        mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName(), 123);
         GbaAuthRequest request = createDefaultRequest();
 
         mTestGbaManager.bootstrapAuthenticationRequest(request);
 
         for (int i = 0; i < GbaManager.MAX_RETRY; i++) {
-            mLooper.processAllMessages();
-            verify(mMockContext, times(i + 1)).bindService(any(), any(), anyInt());
-            try {
-                Thread.sleep(GbaManager.RETRY_TIME_MS + 500);
-            } catch (InterruptedException e) {
-            }
+            processAllMessages();
+            verify(mMockContext, times(i + 1)).bindServiceAsUser(any(), any(), anyInt(),
+                    any(UserHandle.class));
+            moveTimeForward(GbaManager.REQUEST_TIMEOUT_MS);
         }
         assertTrue(!mTestGbaManager.isServiceConnected());
-        mLooper.processAllMessages();
+        processAllMessages();
         verify(mMockCallback).onAuthenticationFailure(anyInt(), anyInt());
     }
 
     @Test
-    @SmallTest
     public void testBindServiceWhenPackageNameChanged() {
-        mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName());
+        mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName(), 123);
         mTestGbaManager.overrideReleaseTime(RELEASE_TIME_60S);
         GbaAuthRequest request = createDefaultRequest();
 
         mTestGbaManager.bootstrapAuthenticationRequest(request);
-        mLooper.processAllMessages();
+        processAllMessages();
         ServiceConnection conn = bindAndConnectService(TEST_DEFAULT_SERVICE_NAME);
-        mTestGbaManager.overrideServicePackage(TEST_SERVICE2_NAME.getPackageName());
+        mTestGbaManager.overrideServicePackage(TEST_SERVICE2_NAME.getPackageName(), 123);
 
         assertEquals(TEST_SERVICE2_NAME.getPackageName(), mTestGbaManager.getServicePackage());
 
-        mLooper.processAllMessages();
+        processAllMessages();
         unbindService(conn);
         bindAndConnectService(TEST_SERVICE2_NAME);
         assertTrue(mTestGbaManager.isServiceConnected());
     }
 
     @Test
-    @SmallTest
     public void testBindServiceWhenReleaseTimeChanged() {
-        mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName());
+        mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName(), 123);
         mTestGbaManager.overrideReleaseTime(RELEASE_NEVER);
 
         assertEquals(RELEASE_NEVER, mTestGbaManager.getReleaseTime());
-        mLooper.processAllMessages();
+        processAllMessages();
         bindAndConnectService(TEST_DEFAULT_SERVICE_NAME);
 
         assertTrue(mTestGbaManager.isServiceConnected());
     }
 
     @Test
-    @SmallTest
     public void testDontBindServiceWhenPackageNameChanged() {
-        mTestGbaManager.overrideServicePackage(TEST_SERVICE2_NAME.getPackageName());
+        mTestGbaManager.overrideServicePackage(TEST_SERVICE2_NAME.getPackageName(), 123);
 
-        mLooper.processAllMessages();
+        processAllMessages();
 
-        verify(mMockContext, never()).bindService(any(), any(), anyInt());
+        verify(mMockContext, never()).bindServiceAsUser(any(), any(), anyInt(),
+                any(UserHandle.class));
         assertTrue(!mTestGbaManager.isServiceConnected());
     }
 
     @Test
-    @SmallTest
     public void testDontBindServiceWhenReleaseTimeChanged() {
-        mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName());
+        mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName(), 123);
         mTestGbaManager.overrideReleaseTime(RELEASE_TIME_60S);
 
-        mLooper.processAllMessages();
+        processAllMessages();
 
-        verify(mMockContext, never()).bindService(any(), any(), anyInt());
+        verify(mMockContext, never()).bindServiceAsUser(any(), any(), anyInt(),
+                any(UserHandle.class));
         assertTrue(!mTestGbaManager.isServiceConnected());
     }
 
     @Test
-    @SmallTest
     public void testMetricsGbaEvent() throws Exception {
-        mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName());
+        mTestGbaManager.overrideServicePackage(TEST_DEFAULT_SERVICE_NAME.getPackageName(), 123);
         mTestGbaManager.overrideReleaseTime(RELEASE_NEVER);
 
-        mLooper.processAllMessages();
+        processAllMessages();
         bindAndConnectService(TEST_DEFAULT_SERVICE_NAME);
         GbaAuthRequest request = createDefaultRequest();
 
         // Failure case
         mTestGbaManager.bootstrapAuthenticationRequest(request);
-        mLooper.processAllMessages();
+        processAllMessages();
 
         ArgumentCaptor<GbaAuthRequest> captor = ArgumentCaptor.forClass(GbaAuthRequest.class);
         verify(mMockGbaServiceBinder, times(1)).authenticationRequest(captor.capture());
@@ -254,7 +247,7 @@
 
         // Success case
         mTestGbaManager.bootstrapAuthenticationRequest(request);
-        mLooper.processAllMessages();
+        processAllMessages();
 
         ArgumentCaptor<GbaAuthRequest> captor2 = ArgumentCaptor.forClass(GbaAuthRequest.class);
         verify(mMockGbaServiceBinder, times(2)).authenticationRequest(captor2.capture());
@@ -280,9 +273,10 @@
                 ArgumentCaptor.forClass(Intent.class);
         ArgumentCaptor<ServiceConnection> serviceCaptor =
                 ArgumentCaptor.forClass(ServiceConnection.class);
-        verify(mMockContext, atLeastOnce()).bindService(intentCaptor.capture(),
+        verify(mMockContext, atLeastOnce()).bindServiceAsUser(intentCaptor.capture(),
                 serviceCaptor.capture(), eq(
-                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE));
+                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                any(UserHandle.class));
         Intent testIntent = intentCaptor.getValue();
         assertEquals(GbaService.SERVICE_INTERFACE, testIntent.getAction());
         assertEquals(component.getPackageName(), testIntent.getPackage());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
index f92643a..a7923cf 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
@@ -1044,7 +1044,8 @@
             doReturn(new TelephonyDisplayInfo(
                     mNetworkTypeController.getDataNetworkType(),
                     mNetworkTypeController.getOverrideNetworkType(),
-                    false)).when(mDisplayInfoController).getTelephonyDisplayInfo();
+                    false, false, false))
+                    .when(mDisplayInfoController).getTelephonyDisplayInfo();
             return null;
         }).when(mDisplayInfoController).updateTelephonyDisplayInfo();
         mNetworkRegistrationInfo = new NetworkRegistrationInfo.Builder()
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
index d8005e8..28d0318 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
@@ -1270,8 +1270,7 @@
         doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords();
         doReturn(refImpuArray).when(mIsimUiccRecords).getIsimImpu();
 
-        List<Uri> impuList = mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG,
-                FEATURE_ID);
+        List<Uri> impuList = mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG);
 
         assertNotNull(impuList);
         assertEquals(refImpuArray.length, impuList.size());
@@ -1288,8 +1287,7 @@
         refImpuArray[2] = "tel:+91987754324";
         doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords();
         doReturn(refImpuArray).when(mIsimUiccRecords).getIsimImpu();
-        List<Uri> impuList = mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG,
-                FEATURE_ID);
+        List<Uri> impuList = mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG);
         assertNotNull(impuList);
         // Null or Empty string cannot be converted to URI
         assertEquals(refImpuArray.length - 2, impuList.size());
@@ -1300,7 +1298,7 @@
         doReturn(null).when(mPhone).getIsimRecords();
 
         try {
-            mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG, FEATURE_ID);
+            mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG);
             fail();
         } catch (Exception ex) {
             assertTrue(ex instanceof IllegalStateException);
@@ -1311,32 +1309,121 @@
     @Test
     public void getImsPublicUserIdentities_InValidSubIdCheck() {
         try {
-            mPhoneSubInfoControllerUT.getImsPublicUserIdentities(-1, TAG, FEATURE_ID);
+            mPhoneSubInfoControllerUT.getImsPublicUserIdentities(-1, TAG);
             fail();
         } catch (Exception ex) {
             assertTrue(ex instanceof IllegalArgumentException);
-            assertTrue(ex.getMessage().contains("Invalid SubscriptionID"));
+            assertTrue(ex.getMessage().contains("Invalid subscription"));
         }
     }
 
     @Test
     public void getImsPublicUserIdentities_NoReadPrivilegedPermission() {
         mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
-        String[] refImpuArray = new String[3];
-        refImpuArray[0] = "012345678";
-        refImpuArray[1] = "sip:test@verify.com";
-        refImpuArray[2] = "tel:+91987754324";
-        doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords();
-        doReturn(refImpuArray).when(mIsimUiccRecords).getIsimImpu();
 
-        List<Uri> impuList = mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG,
-                FEATURE_ID);
+        try {
+            mPhoneSubInfoControllerUT.getImsPublicUserIdentities(0, TAG);
+            fail();
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+            assertTrue(ex.getMessage().contains("getImsPublicUserIdentities"));
+        }
 
-        assertNotNull(impuList);
-        assertEquals(refImpuArray.length, impuList.size());
-        assertEquals(impuList.get(0).toString(), refImpuArray[0]);
-        assertEquals(impuList.get(1).toString(), refImpuArray[1]);
-        assertEquals(impuList.get(2).toString(), refImpuArray[2]);
         mContextFixture.addCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE);
     }
+
+    @Test
+    public void getImsPcscfAddresses() {
+        String[] preDefinedPcscfs = new String[3];
+        preDefinedPcscfs[0] = "127.0.0.1";
+        preDefinedPcscfs[1] = "192.168.0.1";
+        preDefinedPcscfs[2] = "::1";
+        doReturn(true).when(mFeatureFlags).supportIsimRecord();
+        doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords();
+        doReturn(preDefinedPcscfs).when(mIsimUiccRecords).getIsimPcscf();
+
+        List<String> pcscfAddresses = mPhoneSubInfoControllerUT.getImsPcscfAddresses(0, TAG);
+
+        assertNotNull(pcscfAddresses);
+        assertEquals(preDefinedPcscfs.length, pcscfAddresses.size());
+        assertEquals(preDefinedPcscfs[0], pcscfAddresses.get(0).toString());
+        assertEquals(preDefinedPcscfs[1], pcscfAddresses.get(1).toString());
+        assertEquals(preDefinedPcscfs[2], pcscfAddresses.get(2).toString());
+    }
+
+    @Test
+    public void getImsPcscfAddresses_InvalidPcscf() {
+        String[] preDefinedPcscfs = new String[3];
+        preDefinedPcscfs[0] = null;
+        preDefinedPcscfs[2] = "";
+        preDefinedPcscfs[2] = "::1";
+        doReturn(true).when(mFeatureFlags).supportIsimRecord();
+        doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords();
+        doReturn(preDefinedPcscfs).when(mIsimUiccRecords).getIsimPcscf();
+
+        List<String> pcscfAddresses = mPhoneSubInfoControllerUT.getImsPcscfAddresses(0, TAG);
+
+        assertNotNull(pcscfAddresses);
+        // Null or Empty string is not added to pcscf list
+        assertEquals(preDefinedPcscfs.length - 2, pcscfAddresses.size());
+    }
+
+    @Test
+    public void getImsPcscfAddresses_IsimNotLoadedError() {
+        doReturn(true).when(mFeatureFlags).supportIsimRecord();
+        doReturn(null).when(mPhone).getIsimRecords();
+
+        try {
+            mPhoneSubInfoControllerUT.getImsPcscfAddresses(0, TAG);
+            fail();
+        } catch (Exception ex) {
+            assertTrue(ex instanceof IllegalStateException);
+            assertTrue(ex.getMessage().contains("ISIM is not loaded"));
+        }
+    }
+
+    @Test
+    public void getImsPcscfAddresses_InValidSubIdCheck() {
+        doReturn(true).when(mFeatureFlags).supportIsimRecord();
+
+        try {
+            mPhoneSubInfoControllerUT.getImsPcscfAddresses(-1, TAG);
+            fail();
+        } catch (Exception ex) {
+            assertTrue(ex instanceof IllegalArgumentException);
+            assertTrue(ex.getMessage().contains("Invalid subscription"));
+        }
+    }
+
+    @Test
+    public void getImsPcscfAddresses_NoReadPrivilegedPermission() {
+        mContextFixture.removeCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
+        doReturn(true).when(mFeatureFlags).supportIsimRecord();
+
+        try {
+            mPhoneSubInfoControllerUT.getImsPcscfAddresses(0, TAG);
+            fail();
+        } catch (Exception ex) {
+            assertTrue(ex instanceof SecurityException);
+            assertTrue(ex.getMessage().contains("getImsPcscfAddresses"));
+        }
+
+        mContextFixture.addCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE);
+    }
+
+    @Test
+    public void getImsPcscfAddresses_FlagDisabled() {
+        String[] preDefinedPcscfs = new String[3];
+        preDefinedPcscfs[0] = "127.0.0.1";
+        preDefinedPcscfs[1] = "192.168.0.1";
+        preDefinedPcscfs[2] = "::1";
+        doReturn(false).when(mFeatureFlags).supportIsimRecord();
+        doReturn(mIsimUiccRecords).when(mPhone).getIsimRecords();
+        doReturn(preDefinedPcscfs).when(mIsimUiccRecords).getIsimPcscf();
+
+        List<String> pcscfAddresses = mPhoneSubInfoControllerUT.getImsPcscfAddresses(0, TAG);
+
+        assertNotNull(pcscfAddresses);
+        assertEquals(0, pcscfAddresses.size());
+    }
 }
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
index 88c5389..48c9f9c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
@@ -175,6 +175,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.SparseArray;
+import android.view.Display;
 
 import androidx.test.filters.FlakyTest;
 
@@ -312,6 +313,7 @@
         } catch (RuntimeException e) {
         }
         Context context = new ContextFixture().getTestDouble();
+        doReturn(Display.DEFAULT_DISPLAY).when(context).getDisplayId();
         doReturn(true).when(mConnectionManager).isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
         doReturn(mConnectionManager).when(context)
             .getSystemService(Context.CONNECTIVITY_SERVICE);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
index 4c42e2e..05107df 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTest.java
@@ -173,6 +173,7 @@
         rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA, false));
         rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, false));
         rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_NR, false));
+        rats.add(new Pair<Integer, Boolean>(ServiceState.RIL_RADIO_TECHNOLOGY_NB_IOT_NTN, false));
 
         for (Pair<Integer, Boolean> rat : rats) {
             boolean isCdma = rat.second;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index 1465176..60dd9b7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -89,6 +89,8 @@
 import android.telephony.cdma.CdmaCellLocation;
 import android.telephony.gsm.GsmCellLocation;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.telephony.satellite.ISatelliteModemStateCallback;
+import android.telephony.satellite.SatelliteManager;
 import android.text.TextUtils;
 import android.util.Pair;
 
@@ -185,6 +187,7 @@
     private static final String[] CARRIER_CONFIG_PNN = new String[] {
             String.format("%s,%s", HOME_PNN, "short"), "f2,s2"
     };
+    private static final String SATELLITE_DISPLAY_NAME = "SatelliteTest";
 
     private class ServiceStateTrackerTestHandler extends HandlerThread {
 
@@ -391,6 +394,11 @@
                     30  /* SIGNAL_STRENGTH_GREAT */
                 });
 
+        // satellite display name
+        mBundle.putString(
+                CarrierConfigManager.KEY_SATELLITE_DISPLAY_NAME_STRING,
+                SATELLITE_DISPLAY_NAME);
+
         sendCarrierConfigUpdate(PHONE_ID);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
@@ -2983,7 +2991,7 @@
         doReturn(ServiceState.STATE_IN_SERVICE).when(mSST).getCombinedRegState(ss);
 
         // update the spn
-        sst.updateSpnDisplay();
+        sst.updateCarrierDisplayName();
 
         // Plmn should be shown, and the string is "Emergency call only"
         Bundle b = getExtrasFromLastSpnUpdateIntent();
@@ -3005,7 +3013,7 @@
         sst.mSS = ss;
 
         // update the spn
-        sst.updateSpnDisplay();
+        sst.updateCarrierDisplayName();
 
         // Plmn should be shown, and the string is "No service"
         Bundle b = getExtrasFromLastSpnUpdateIntent();
@@ -3026,7 +3034,7 @@
         sst.mSS = ss;
 
         // update the spn
-        sst.updateSpnDisplay();
+        sst.updateCarrierDisplayName();
 
         // Plmn should be shown, and the string is null
         Bundle b = getExtrasFromLastSpnUpdateIntent();
@@ -3049,7 +3057,7 @@
         doReturn(false).when(mPhone).isWifiCallingEnabled();
 
         // update the spn
-        sst.updateSpnDisplay();
+        sst.updateCarrierDisplayName();
 
         // Show both spn & plmn
         String spn = mBundle.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
@@ -3091,7 +3099,7 @@
         doReturn(formats).when(r).getStringArray(anyInt());
 
         // update the spn
-        sst.updateSpnDisplay();
+        sst.updateCarrierDisplayName();
 
         // Only spn should be shown
         String spn = mBundle.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
@@ -3128,7 +3136,7 @@
         doReturn(true).when(mPhone).isImsRegistered();
 
         // update the spn
-        sst.updateSpnDisplay();
+        sst.updateCarrierDisplayName();
 
         // Only spn should be shown
         String spn = mBundle.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
@@ -3161,7 +3169,7 @@
         doReturn(true).when(mPhone).isImsRegistered();
 
         // update the spn
-        sst.updateSpnDisplay();
+        sst.updateCarrierDisplayName();
 
         // Only plmn should be shown
         String plmn = mBundle.getStringArray(CarrierConfigManager.KEY_PNN_OVERRIDE_STRING_ARRAY)[0];
@@ -3187,7 +3195,7 @@
         doReturn(false).when(mPhone).isWifiCallingEnabled();
 
         // update the spn
-        sst.updateSpnDisplay();
+        sst.updateCarrierDisplayName();
 
         // Show both spn & plmn
         String spn = mBundle.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
@@ -3240,7 +3248,7 @@
         doReturn(false).when(mPhone).isWifiCallingEnabled();
 
         // update the spn
-        sst.updateSpnDisplay();
+        sst.updateCarrierDisplayName();
 
         // Plmn should be shown, and the string is "No service"
         Bundle b = getExtrasFromLastSpnUpdateIntent();
@@ -3271,7 +3279,7 @@
         doReturn(true).when(mPhone).isWifiCallingEnabled();
 
         // update the spn
-        sst.updateSpnDisplay();
+        sst.updateCarrierDisplayName();
 
         // Plmn should be shown, and the string is "No service"
         Bundle b = getExtrasFromLastSpnUpdateIntent();
@@ -3303,7 +3311,7 @@
         doReturn(true).when(mPhone).isWifiCallingEnabled();
 
         // update the spn
-        sst.updateSpnDisplay();
+        sst.updateCarrierDisplayName();
 
         // Plmn should be shown, and the string is "No service"
         Bundle b = getExtrasFromLastSpnUpdateIntent();
@@ -3503,7 +3511,7 @@
         doReturn(Arrays.asList("10123")).when(mSatelliteController).getSatellitePlmnsForCarrier(
                 anyInt());
         doReturn(satelliteSupportedServiceList).when(mSatelliteController)
-                .getSupportedSatelliteServices(sst.mSubId, "10123");
+                .getSupportedSatelliteServicesForPlmn(sst.mSubId, "10123");
 
         assertFalse(sst.mSS.isUsingNonTerrestrialNetwork());
 
@@ -3546,4 +3554,70 @@
             }
         }
     }
+
+    @Test
+    public void testSatelliteModemStateCallback() throws Exception {
+        ArgumentCaptor<ISatelliteModemStateCallback> captor =
+                ArgumentCaptor.forClass(ISatelliteModemStateCallback.class);
+        verify(mSatelliteController, times(1)).registerForSatelliteModemStateChanged(
+                captor.capture());
+        ISatelliteModemStateCallback callback = captor.getValue();
+
+        doReturn(1).when(mSatelliteController).getSelectedSatelliteSubId();
+        doReturn(1).when(mPhone).getSubId();
+
+        mSimulatedCommands.setVoiceRegState(
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING);
+        mSimulatedCommands.setVoiceRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
+        mSimulatedCommands.setDataRegState(
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING);
+        mSimulatedCommands.setDataRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN);
+        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState();
+        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getDataRegistrationState();
+        sst.mSS = mServiceState;
+
+        mBundle.putBoolean(
+                CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL, false);
+        sendCarrierConfigUpdate(PHONE_ID);
+
+        doReturn(true).when(mSatelliteController).isInConnectedState();
+        callback.onSatelliteModemStateChanged(SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+
+        // update the spn
+        sst.updateCarrierDisplayName();
+
+        Bundle b = getExtrasFromLastSpnUpdateIntent();
+        assertThat(b.getString(TelephonyManager.EXTRA_PLMN)).isEqualTo(SATELLITE_DISPLAY_NAME);
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_PLMN)).isTrue();
+
+        // Override operator name to "Satellite" when registration state is IN_SERVICE.
+        mSimulatedCommands.setVoiceRegState(
+                NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        mSimulatedCommands.setVoiceRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        mSimulatedCommands.setDataRegState(
+                NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+        mSimulatedCommands.setDataRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_LTE);
+        doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState();
+        doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getDataRegistrationState();
+        doReturn("Skylo Technologies").when(mServiceState).getOperatorAlpha();
+
+        mBundle.putBoolean(
+                CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL, false);
+        mBundle.putBoolean(CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, true);
+        mBundle.putString(CarrierConfigManager.KEY_CARRIER_NAME_STRING, "");
+        sendCarrierConfigUpdate(PHONE_ID);
+
+        callback.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
+
+        // update the spn
+        sst.updateCarrierDisplayName();
+
+        b = getExtrasFromLastSpnUpdateIntent();
+        assertThat(b.getString(TelephonyManager.EXTRA_PLMN)).isEqualTo(SATELLITE_DISPLAY_NAME);
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_PLMN)).isTrue();
+    }
+
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java
index 054df07..6fd45ea 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java
@@ -134,7 +134,6 @@
                 .getSubscriptionInfo(any(Integer.class));
         doReturn(RIL.RADIO_HAL_VERSION_2_2).when(mMockRadioConfigProxy).getVersion();
         doReturn(true).when(mFeatureFlags).simultaneousCallingIndications();
-        doReturn(true).when(mFeatureFlags).dataOnlyCellularService();
         mMockRegistryManager = mContext.getSystemService(TelephonyRegistryManager.class);
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
index acf793e..15e6ee2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
@@ -25,6 +25,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.isNull;
@@ -116,17 +117,23 @@
 
         public void testNotifySmsSentToEmergencyStateTracker(String destAddr, long messageId,
                 boolean isOverIms, boolean isLastSmsPart) {
-            notifySmsSent(destAddr, messageId, isOverIms, isLastSmsPart, true/*success*/);
+            notifySmsSent(getSmsTracker(destAddr, messageId), isOverIms,
+                isLastSmsPart, true/*success*/);
         }
 
         public void testNotifySmsSentFailedToEmergencyStateTracker(String destAddr,
                 long messageId, boolean isOverIms) {
-            notifySmsSent(destAddr, messageId, isOverIms, true/*isLastSmsPart*/, false/*success*/);
+            notifySmsSent(getSmsTracker(destAddr, messageId), isOverIms,
+                true/*isLastSmsPart*/, false/*success*/);
         }
 
         public void testNotifySmsReceivedViaImsToEmergencyStateTracker(String origAddr) {
             notifySmsReceivedViaImsToEmergencyStateTracker(origAddr);
         }
+
+        private SMSDispatcher.SmsTracker getSmsTracker(String destAddr, long messageId) {
+            return new SMSDispatcher.SmsTracker(destAddr, messageId, "testMessage");
+        }
     }
 
     /**
@@ -140,9 +147,9 @@
         @Override
         public void sendData(String callingPackage, int callingUser, String destAddr,
                 String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
-                PendingIntent deliveryIntent, boolean isForVvm) {
+                PendingIntent deliveryIntent, boolean isForVvm, long uniqueMessageId) {
             super.sendData(callingPackage, callingUser, destAddr, scAddr, destPort,
-                    data, sentIntent, deliveryIntent, isForVvm);
+                    data, sentIntent, deliveryIntent, isForVvm, uniqueMessageId);
         }
 
         @Override
@@ -167,9 +174,9 @@
         @Override
         public void sendData(String callingPackage, int callingUser, String destAddr, String scAddr,
                 int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
-                boolean isForVvm) {
+                boolean isForVvm, long uniqueMessageId) {
             super.sendData(callingPackage, callingUser, destAddr, scAddr, destPort,
-                    data, sentIntent, deliveryIntent, isForVvm);
+                    data, sentIntent, deliveryIntent, isForVvm, uniqueMessageId);
         }
 
         @Override
@@ -491,7 +498,7 @@
         verify(mEmergencySmsDsc).finishSelection();
         verify(mImsSmsDispatcher).sendText(eq("911"), eq("2222"), eq("text"), eq(mSentIntent),
                 any(), any(), eq("test-app"), eq(mCallingUserId), eq(false),
-                eq(0), eq(false), eq(10), eq(false), eq(1L), eq(false));
+                eq(0), eq(false), eq(10), eq(false), eq(1L), eq(false), anyLong());
         assertNull(holder.getConnection());
         assertFalse(holder.isDomainSelectionRequested());
         assertEquals(0, holder.getPendingRequests().size());
@@ -542,7 +549,7 @@
         verify(mImsSmsDispatcher).sendMultipartText(eq("911"), eq("2222"), eq(parts),
                 eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"),
                 eq(mCallingUserId), eq(false), eq(0), eq(false),
-                eq(10), eq(1L));
+                eq(10), eq(1L), anyLong());
         assertNull(holder.getConnection());
         assertFalse(holder.isDomainSelectionRequested());
         assertEquals(0, holder.getPendingRequests().size());
@@ -741,7 +748,7 @@
 
         verify(mImsSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent),
                 any(), any(), eq("test-app"), eq(mCallingUserId),
-                eq(false), eq(0), eq(false), eq(10), eq(false), eq(1L), eq(false));
+                eq(false), eq(0), eq(false), eq(10), eq(false), eq(1L), eq(false), anyLong());
     }
 
     @Test
@@ -782,7 +789,7 @@
 
         verify(mImsSmsDispatcher).sendText(eq("911"), eq("2222"), eq("text"), eq(mSentIntent),
                 any(), any(), eq("test-app"), eq(0), eq(false), eq(0), eq(false), eq(10),
-                eq(false), eq(1L), eq(false));
+                eq(false), eq(1L), eq(false), anyLong());
     }
 
     @Test
@@ -818,7 +825,7 @@
         verify(mImsSmsDispatcher, times(2)).sendText(eq("1111"), eq("2222"),
                 eq("text"), eq(mSentIntent), any(), any(), eq("test-app"), eq(mCallingUserId),
                 eq(false), eq(0), eq(false), eq(10), eq(false), eq(1L),
-                eq(false));
+                eq(false), anyLong());
         assertNull(holder.getConnection());
         assertFalse(holder.isDomainSelectionRequested());
         assertEquals(0, holder.getPendingRequests().size());
@@ -839,7 +846,7 @@
         // ImsSmsDispatcher handles this text directly.
         verify(mImsSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"),
                 eq(mSentIntent), any(), any(), eq("test-app"), eq(mCallingUserId), eq(false), eq(0),
-                eq(false), eq(10), eq(false), eq(1L), eq(false));
+                eq(false), eq(10), eq(false), eq(1L), eq(false), anyLong());
     }
 
     @Test
@@ -903,7 +910,7 @@
         verify(newSmsDsc).finishSelection();
         verify(mImsSmsDispatcher, times(2)).sendText(eq("1111"), eq("2222"), eq("text"),
                 eq(mSentIntent), any(), any(), eq("test-app"), eq(mCallingUserId), eq(false), eq(0),
-                eq(false), eq(10), eq(false), eq(1L), eq(false));
+                eq(false), eq(10), eq(false), eq(1L), eq(false), anyLong());
         assertNull(holder.getConnection());
         assertFalse(holder.isDomainSelectionRequested());
         assertEquals(0, holder.getPendingRequests().size());
@@ -974,7 +981,7 @@
         verify(newEmergencySmsDsc).finishSelection();
         verify(mImsSmsDispatcher, times(2)).sendText(eq("911"), eq("2222"), eq("text"),
                 eq(mSentIntent), any(), any(), eq("test-app"), eq(0), eq(false), eq(0), eq(false),
-                eq(10), eq(false), eq(1L), eq(false));
+                eq(10), eq(false), eq(1L), eq(false), anyLong());
         assertNull(holder.getConnection());
         assertFalse(holder.isDomainSelectionRequested());
         assertEquals(0, holder.getPendingRequests().size());
@@ -1017,7 +1024,7 @@
 
         verify(mImsSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent),
                 any(), any(), eq("test-app"), eq(mCallingUserId), eq(false), eq(0),
-                eq(false), eq(10), eq(false), eq(1L), eq(false));
+                eq(false), eq(10), eq(false), eq(1L), eq(false), anyLong());
     }
 
     @Test
@@ -1060,7 +1067,7 @@
 
         verify(mImsSmsDispatcher).sendText(eq("911"), eq("2222"), eq("text"), eq(mSentIntent),
                 any(), any(), eq("test-app"), eq(mCallingUserId), eq(false), eq(0), eq(false),
-                eq(10), eq(false), eq(1L), eq(false));
+                eq(10), eq(false), eq(1L), eq(false), anyLong());
     }
 
     private void switchImsSmsFormat(int phoneType) {
@@ -1079,7 +1086,8 @@
 
     @Test
     public void testSendSmsToDatagramDispatcher() {
-        when(mSatelliteController.isInCarrierRoamingNbIotNtn()).thenReturn(true);
+        when(mSatelliteController.shouldSendSmsToDatagramDispatcher(any(Phone.class)))
+                .thenReturn(true);
         mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
                 "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
         processAllMessages();
@@ -1219,13 +1227,13 @@
         verify(mSmsDsc).finishSelection();
         if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
             verify(mImsSmsDispatcher).sendData(eq("test-app"), eq(0), eq("1111"), eq("2222"),
-                    eq(8080), eq(data), eq(mSentIntent), any(), eq(false));
+                    eq(8080), eq(data), eq(mSentIntent), any(), eq(false), anyLong());
         } else if (isCdmaMo) {
             verify(mCdmaSmsDispatcher).sendData(eq("test-app"), eq(0), eq("1111"), eq("2222"),
-                    eq(8080), eq(data), eq(mSentIntent), any(), eq(false));
+                    eq(8080), eq(data), eq(mSentIntent), any(), eq(false), anyLong());
         } else {
             verify(mGsmSmsDispatcher).sendData(eq("test-app"), eq(0), eq("1111"), eq("2222"),
-                    eq(8080), eq(data), eq(mSentIntent), any(), eq(false));
+                    eq(8080), eq(data), eq(mSentIntent), any(), eq(false), anyLong());
         }
         assertNull(holder.getConnection());
         assertFalse(holder.isDomainSelectionRequested());
@@ -1256,15 +1264,15 @@
         if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
             verify(mImsSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent),
                     any(), any(), eq("test-app"), eq(0), eq(false), eq(0), eq(false), eq(10),
-                    eq(false), eq(1L), eq(false));
+                    eq(false), eq(1L), eq(false), anyLong());
         } else if (isCdmaMo) {
             verify(mCdmaSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent),
                     any(), any(), eq("test-app"), eq(0), eq(false), eq(0), eq(false), eq(10),
-                    eq(false), eq(1L), eq(false));
+                    eq(false), eq(1L), eq(false), anyLong());
         } else {
             verify(mGsmSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent),
                     any(), any(), eq("test-app"), eq(0), eq(false), eq(0), eq(false), eq(10),
-                    eq(false), eq(1L), eq(false));
+                    eq(false), eq(1L), eq(false), anyLong());
         }
         assertNull(holder.getConnection());
         assertFalse(holder.isDomainSelectionRequested());
@@ -1298,16 +1306,16 @@
         if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
             verify(mImsSmsDispatcher).sendMultipartText(eq("1111"), eq("2222"), eq(parts),
                     eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"), eq(mCallingUserId),
-                    eq(false), eq(0), eq(false), eq(10), eq(1L));
+                    eq(false), eq(0), eq(false), eq(10), eq(1L), anyLong());
         } else if (isCdmaMo) {
             verify(mCdmaSmsDispatcher).sendMultipartText(eq("1111"), eq("2222"), eq(parts),
                     eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"), eq(mCallingUserId),
                     eq(false), eq(0),
-                    eq(false), eq(10), eq(1L));
+                    eq(false), eq(10), eq(1L), anyLong());
         } else {
             verify(mGsmSmsDispatcher).sendMultipartText(eq("1111"), eq("2222"), eq(parts),
                     eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"), eq(mCallingUserId),
-                    eq(false), eq(0), eq(false), eq(10), eq(1L));
+                    eq(false), eq(0), eq(false), eq(10), eq(1L), anyLong());
         }
         assertNull(holder.getConnection());
         assertFalse(holder.isDomainSelectionRequested());
@@ -1384,6 +1392,6 @@
                 SmsDispatchersController.PendingRequest.TYPE_TEXT, null, "test-app",
                 mCallingUserId, "1111", "2222", asArrayList(mSentIntent), asArrayList(null),
                 false, null, 0, asArrayList("text"), null,
-                false, 0, false, 10, 100L, false);
+                false, 0, false, 10, 100L, false, false);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java
index dc1ee63..1846bae 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsMessageBodyTest.java
@@ -16,13 +16,17 @@
 
 package com.android.internal.telephony;
 
-import android.telephony.TelephonyManager;
-import android.test.AndroidTestCase;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
-import androidx.test.filters.SmallTest;
+import android.telephony.TelephonyManager;
 
 import com.android.telephony.Rlog;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
 /**
  * Test cases to verify selection of the optimal 7 bit encoding tables
  * (for all combinations of enabled national language tables) for messages
@@ -33,7 +37,7 @@
  * Tests both encoding variations: unsupported characters mapped to space,
  * and unsupported characters force entire message to UCS-2.
  */
-public class SmsMessageBodyTest extends AndroidTestCase {
+public class SmsMessageBodyTest extends TelephonyTest {
     private static final String TAG = "SmsMessageBodyTest";
 
     // ASCII chars in the GSM 7 bit default alphabet
@@ -250,7 +254,12 @@
      */
     private static final int UDH_SEPTET_COST_CONCATENATED_MESSAGE = 6;
 
-    @SmallTest
+    @Before
+    public void setUp() {
+        TelephonyManager.setupISmsForTest(Mockito.mock(ISms.class));
+    }
+
+    @Test
     public void testCalcLengthAscii() throws Exception {
         StringBuilder sb = new StringBuilder(320);
         int[] values = {0, 0, 0, SmsConstants.ENCODING_7BIT, 0, 0};
@@ -282,7 +291,7 @@
         }
     }
 
-    @SmallTest
+    @Test
     public void testCalcLengthUnicode() throws Exception {
         StringBuilder sb = new StringBuilder(160);
         int[] values = {0, 0, 0, SmsConstants.ENCODING_16BIT, 0, 0};
@@ -482,7 +491,7 @@
         }
     }
 
-    //@LargeTest
+    //@Test
     /*public void testCalcLengthMixed7bit() throws Exception {
         StringBuilder sb = new StringBuilder(320);
         CounterHelper ch = new CounterHelper();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsUsageMonitorShortCodeTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsUsageMonitorShortCodeTest.java
index 3b637c9..1e1e43f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsUsageMonitorShortCodeTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsUsageMonitorShortCodeTest.java
@@ -26,6 +26,8 @@
 
 import android.os.Looper;
 
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
+
 import org.junit.Ignore;
 
 /**
@@ -465,7 +467,8 @@
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
-        SmsUsageMonitor monitor = new SmsUsageMonitor(TestApplication.getAppContext());
+        SmsUsageMonitor monitor = new SmsUsageMonitor(TestApplication.getAppContext(),
+                new FeatureFlagsImpl());
         for (ShortCodeTest test : sShortCodeTests) {
             assertEquals("country: " + test.countryIso + " number: " + test.address,
                     test.category, monitor.checkDestination(test.address, test.countryIso));
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
index 1e2b8ce..99ece85 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
@@ -48,7 +48,6 @@
 
     @Before
     public void setUp() throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE);
         mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG);
         mSubscriptionInfoUT = new SubscriptionInfo.Builder()
                 .setId(1)
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyCountryDetectorTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyCountryDetectorTest.java
index 9b3777b..5db1206 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyCountryDetectorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyCountryDetectorTest.java
@@ -112,26 +112,27 @@
         when(mSST2.getLocaleTracker()).thenReturn(mMockLocaleTracker2);
         when(mMockLocaleTracker2.getCurrentCountry()).thenReturn("");
 
-        when(mConnectivityManager.getActiveNetwork()).thenReturn(mMockNetwork);
         mNetworkCapabilities = new NetworkCapabilities();
         mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, true);
         mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET, true);
-        when(mConnectivityManager.getNetworkCapabilities(any(Network.class)))
-                .thenReturn(mNetworkCapabilities);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED, true);
 
         when(mLocationManager.getProviders(true)).thenReturn(Arrays.asList("TEST_PROVIDER"));
 
         when(mMockFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
         mCountryDetectorUT = new TestTelephonyCountryDetector(
                 mLooper, mContext, mLocationManager, mConnectivityManager, mMockFeatureFlags);
+        verify(mConnectivityManager).registerNetworkCallback(
+                any(NetworkRequest.class), mNetworkCallbackCaptor.capture());
+        mNetworkCallbackCaptor.getValue().onAvailable(mMockNetwork);
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(mMockNetwork, mNetworkCapabilities);
+        mTestableLooper.processAllMessages();
         if (isGeoCoderImplemented()) {
             verify(mLocationManager).requestLocationUpdates(anyString(), anyLong(), anyFloat(),
                     mLocationListenerCaptor.capture());
             verify(mLocationManager).getProviders(true);
             verify(mLocationManager).getLastKnownLocation(anyString());
         }
-        verify(mConnectivityManager).registerNetworkCallback(
-                any(NetworkRequest.class), mNetworkCallbackCaptor.capture());
     }
 
     @After
@@ -285,7 +286,7 @@
 
         // Wi-fi is not available
         clearInvocations(mLocationManager);
-        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, false);
+        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, true);
         mNetworkCallbackCaptor.getValue().onLost(mMockNetwork);
         mTestableLooper.processAllMessages();
         verify(mLocationManager, never()).removeUpdates(any(LocationListener.class));
@@ -294,6 +295,9 @@
         clearInvocations(mLocationManager);
         mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, true);
         mNetworkCallbackCaptor.getValue().onAvailable(mMockNetwork);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET, true);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED, true);
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(mMockNetwork, mNetworkCapabilities);
         mTestableLooper.processAllMessages();
         // Location updates were already requested
         verify(mLocationManager, never()).requestLocationUpdates(anyString(), anyLong(), anyFloat(),
@@ -301,7 +305,10 @@
 
         // Make Wi-fi not available and reset the quota
         clearInvocations(mLocationManager);
-        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, false);
+        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, true);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET, true);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED, false);
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(mMockNetwork, mNetworkCapabilities);
         mTestableLooper.moveTimeForward(
                 TestTelephonyCountryDetector.getLocationUpdateRequestQuotaResetTimeoutMillis());
         mTestableLooper.processAllMessages();
@@ -311,6 +318,9 @@
         clearInvocations(mLocationManager);
         mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, true);
         mNetworkCallbackCaptor.getValue().onAvailable(mMockNetwork);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET, true);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED, true);
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(mMockNetwork, mNetworkCapabilities);
         mTestableLooper.processAllMessages();
         verify(mLocationManager).requestLocationUpdates(anyString(), anyLong(), anyFloat(),
                 any(LocationListener.class));
@@ -324,16 +334,20 @@
         verify(mLocationManager, never()).requestLocationUpdates(anyString(), anyLong(), anyFloat(),
                 any(LocationListener.class));
 
-        // Wi-fi becomes not available
+        // Wi-fi lost
         clearInvocations(mLocationManager);
-        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, false);
-        mNetworkCallbackCaptor.getValue().onUnavailable();
+        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, true);
+        mNetworkCallbackCaptor.getValue().onLost(mMockNetwork);
         mTestableLooper.processAllMessages();
         verify(mLocationManager).removeUpdates(any(LocationListener.class));
     }
 
     @Test
     public void testRegisterUnregisterForWifiConnectivityStateChanged() {
+        // Set Wi-Fi unavailable.
+        mNetworkCallbackCaptor.getValue().onLost(mMockNetwork);
+        mTestableLooper.processAllMessages();
+
         WifiConnectivityStateChangedListener listener = new WifiConnectivityStateChangedListener(
                 mLooper);
 
@@ -344,19 +358,63 @@
         clearInvocations(mLocationManager);
         mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, true);
         mNetworkCallbackCaptor.getValue().onAvailable(mMockNetwork);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET, true);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED, true);
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(mMockNetwork, mNetworkCapabilities);
         mTestableLooper.processAllMessages();
         assertTrue(listener.getIsWifiConnected());
 
-        // Wi-fi becomes not available
+        // Wi-fi lost
         clearInvocations(mLocationManager);
-        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, false);
-        mNetworkCallbackCaptor.getValue().onUnavailable();
+        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, true);
+        mNetworkCallbackCaptor.getValue().onLost(mMockNetwork);
         mTestableLooper.processAllMessages();
         assertFalse(listener.getIsWifiConnected());
 
         mCountryDetectorUT.unregisterForWifiConnectivityStateChanged(listener);
     }
 
+    @Test
+    public void testReflectWifiConnectedStatusChanged() {
+        // 1. Wi-Fi is turned off, network capability is not available.
+        mNetworkCallbackCaptor.getValue().onLost(mMockNetwork);
+        mNetworkCapabilities = new NetworkCapabilities();
+        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, true);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET, false);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED, false);
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(mMockNetwork, mNetworkCapabilities);
+        mTestableLooper.processAllMessages();
+        assertFalse(mCountryDetectorUT.isWifiNetworkConnected());
+
+        // 2. Wi-Fi is turned on, but network capability has not been updated.
+        mNetworkCallbackCaptor.getValue().onAvailable(mMockNetwork);
+        mTestableLooper.processAllMessages();
+        assertFalse(mCountryDetectorUT.isWifiNetworkConnected());
+
+        // 3. Network capability has been updated, not some of them still false.
+        mNetworkCapabilities = new NetworkCapabilities();
+        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, true);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET, true);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED, false);
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(mMockNetwork, mNetworkCapabilities);
+        mTestableLooper.processAllMessages();
+        assertFalse(mCountryDetectorUT.isWifiNetworkConnected());
+
+        // 4. Network capability has been updated to validated.
+        mNetworkCapabilities = new NetworkCapabilities();
+        mNetworkCapabilities.setTransportType(NetworkCapabilities.TRANSPORT_WIFI, true);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET, true);
+        mNetworkCapabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED, true);
+        mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(mMockNetwork, mNetworkCapabilities);
+        mTestableLooper.processAllMessages();
+        assertTrue(mCountryDetectorUT.isWifiNetworkConnected());
+
+        // 5. Wi-Fi is turned off.
+        mNetworkCallbackCaptor.getValue().onLost(mMockNetwork);
+        mTestableLooper.processAllMessages();
+        assertFalse(mCountryDetectorUT.isWifiNetworkConnected());
+    }
+
     private static boolean isGeoCoderImplemented() {
         return Geocoder.isPresent();
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
index a645439..e5aa541 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
@@ -15,7 +15,12 @@
  */
 package com.android.internal.telephony;
 
+import static android.telephony.CellularIdentifierDisclosure.CELLULAR_IDENTIFIER_IMSI;
+import static android.telephony.CellularIdentifierDisclosure.NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST;
 import static android.telephony.PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
+import static android.telephony.SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP;
+import static android.telephony.SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2;
+import static android.telephony.SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96;
 import static android.telephony.ServiceState.FREQUENCY_RANGE_LOW;
 import static android.telephony.SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -42,6 +47,7 @@
 import android.content.pm.UserInfo;
 import android.net.LinkProperties;
 import android.os.Build;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -50,17 +56,20 @@
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation;
 import android.telephony.BarringInfo;
+import android.telephony.CallState;
 import android.telephony.CellIdentity;
 import android.telephony.CellIdentityGsm;
 import android.telephony.CellIdentityLte;
 import android.telephony.CellInfo;
 import android.telephony.CellInfoLte;
 import android.telephony.CellLocation;
+import android.telephony.CellularIdentifierDisclosure;
 import android.telephony.LinkCapacityEstimate;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseDataConnectionState;
+import android.telephony.SecurityAlgorithmUpdate;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -68,6 +77,7 @@
 import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
+import android.telephony.satellite.NtnSignalStrength;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.text.TextUtils;
@@ -86,6 +96,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -103,6 +114,7 @@
     // Mocked classes
     private SubscriptionInfo mMockSubInfo;
     private TelephonyRegistry.ConfigurationProvider mMockConfigurationProvider;
+    private IBinder mMockIBinder;
 
     private TelephonyCallbackWrapper mTelephonyCallback;
     private List<LinkCapacityEstimate> mLinkCapacityEstimateList;
@@ -123,8 +135,14 @@
     private CellIdentity mCellIdentityForRegiFail;
     private int mRegistrationFailReason;
     private Set<Integer> mSimultaneousCallingSubscriptions;
+    private int mCallbackModeStopReason = TelephonyManager.STOP_REASON_UNKNOWN;
+    private long mCallbackModeDurationMillis;
     private boolean mCarrierRoamingNtnMode;
     private boolean mCarrierRoamingNtnEligible;
+    private int[] mCarrierRoamingNtnAvailableServices;
+    private NtnSignalStrength mCarrierRoamingNtnSignalStrength;
+    private boolean mIsSatelliteEnabled;
+    private final List<List<CallState>> mCallStateList = new ArrayList<>();
 
     // All events contribute to TelephonyRegistry#isPhoneStatePermissionRequired
     private static final Set<Integer> READ_PHONE_STATE_EVENTS;
@@ -172,6 +190,10 @@
                 TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED);
         READ_PRIVILEGED_PHONE_STATE_EVENTS.add(
                 TelephonyCallback.EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED);
+        READ_PRIVILEGED_PHONE_STATE_EVENTS.add(
+                TelephonyCallback.EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED);
+        READ_PRIVILEGED_PHONE_STATE_EVENTS.add(
+                TelephonyCallback.EVENT_SECURITY_ALGORITHMS_CHANGED);
     }
 
     // All events contribute to TelephonyRegistry#isActiveEmergencySessionPermissionRequired
@@ -201,7 +223,11 @@
             TelephonyCallback.RegistrationFailedListener,
             TelephonyCallback.DataActivityListener,
             TelephonyCallback.SimultaneousCellularCallingSupportListener,
-            TelephonyCallback.CarrierRoamingNtnModeListener {
+            TelephonyCallback.EmergencyCallbackModeListener,
+            TelephonyCallback.CarrierRoamingNtnModeListener,
+            TelephonyCallback.SecurityAlgorithmsListener,
+            TelephonyCallback.CellularIdentifierDisclosedListener,
+            TelephonyCallback.CallAttributesListener {
         // This class isn't mockable to get invocation counts because the IBinder is null and
         // crashes the TelephonyRegistry. Make a cheesy verify(times()) alternative.
         public AtomicInteger invocationCount = new AtomicInteger(0);
@@ -298,6 +324,27 @@
         }
 
         @Override
+        public void onCallbackModeStarted(@TelephonyManager.EmergencyCallbackModeType int type,
+                @NonNull Duration timerDuration, int subId) {
+            invocationCount.incrementAndGet();
+            mCallbackModeDurationMillis = timerDuration.toMillis();
+        }
+
+        @Override
+        public void onCallbackModeRestarted(@TelephonyManager.EmergencyCallbackModeType int type,
+                @NonNull Duration timerDuration, int subId) {
+            invocationCount.incrementAndGet();
+            mCallbackModeDurationMillis = timerDuration.toMillis();
+        }
+
+        @Override
+        public void onCallbackModeStopped(@TelephonyManager.EmergencyCallbackModeType int type,
+                @TelephonyManager.EmergencyCallbackModeStopReason int reason, int subId) {
+            invocationCount.incrementAndGet();
+            mCallbackModeStopReason = reason;
+        }
+
+        @Override
         public void onCarrierRoamingNtnModeChanged(boolean active) {
             invocationCount.incrementAndGet();
             mCarrierRoamingNtnMode = active;
@@ -308,6 +355,46 @@
             invocationCount.incrementAndGet();
             mCarrierRoamingNtnEligible = eligible;
         }
+
+        @Override
+        public void onCarrierRoamingNtnAvailableServicesChanged(int[] services) {
+            invocationCount.incrementAndGet();
+            mCarrierRoamingNtnAvailableServices = services;
+        }
+
+        @Override
+        public void onCarrierRoamingNtnSignalStrengthChanged(NtnSignalStrength ntnSignalStrength) {
+            invocationCount.incrementAndGet();
+            mCarrierRoamingNtnSignalStrength = ntnSignalStrength;
+        }
+
+        @Override
+        public void onSecurityAlgorithmsChanged(SecurityAlgorithmUpdate update) {
+            invocationCount.incrementAndGet();
+        }
+
+        @Override
+        public void onCellularIdentifierDisclosedChanged(CellularIdentifierDisclosure disclosure) {
+            invocationCount.incrementAndGet();
+        }
+
+        @Override
+        public void onCallStatesChanged(List<CallState> callStateList) {
+            invocationCount.incrementAndGet();
+            mCallStateList.add(callStateList);
+        }
+    }
+
+    public class MySatelliteStateChangeListener implements ISatelliteStateChangeListener {
+        @Override
+        public void onSatelliteEnabledStateChanged(boolean isEnabled) throws RemoteException {
+            mIsSatelliteEnabled = isEnabled;
+        }
+
+        @Override
+        public IBinder asBinder() {
+            return mMockIBinder;
+        }
     }
 
     private void addTelephonyRegistryService() {
@@ -322,6 +409,7 @@
         super.setUp(getClass().getSimpleName());
         mMockSubInfo = mock(SubscriptionInfo.class);
         mMockConfigurationProvider = mock(TelephonyRegistry.ConfigurationProvider.class);
+        mMockIBinder = mock(IBinder.class);
         when(mMockConfigurationProvider.getRegistrationLimit()).thenReturn(-1);
         when(mMockConfigurationProvider.isRegistrationLimitEnabledInPlatformCompat(anyInt()))
                 .thenReturn(false);
@@ -349,6 +437,7 @@
         processAllMessages();
         assertEquals(mTelephonyRegistry.asBinder(),
                 ServiceManager.getService("telephony.registry"));
+        doReturn(new int[]{1}).when(mSubscriptionManager).getActiveSubscriptionIdList();
     }
 
     @After
@@ -368,6 +457,7 @@
             mPhysicalChannelConfigs = null;
         }
         mCellLocation = null;
+        mCallStateList.clear();
         super.tearDown();
     }
 
@@ -981,6 +1071,8 @@
 
         mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU;
         doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(anyString(), anyInt());
+        doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfoAsUser(
+                anyString(), anyInt(), any(UserHandle.class));
         mContextFixture.addCallingOrSelfPermission("");
         mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
         mContextFixture.addCallingOrSelfPermission(
@@ -1077,6 +1169,8 @@
 
         mApplicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU;
         doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(anyString(), anyInt());
+        doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfoAsUser(
+                anyString(), anyInt(), any(UserHandle.class));
         mContextFixture.addCallingOrSelfPermission("");
         mContextFixture.addCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
         mContextFixture.addCallingOrSelfPermission(
@@ -1260,7 +1354,7 @@
         TelephonyDisplayInfo displayInfo = new TelephonyDisplayInfo(
                 TelephonyManager.NETWORK_TYPE_LTE,
                 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
-                false);
+                false, false, false);
 
         // Notify with invalid subId on default phone. Should NOT trigger callback.
         mTelephonyRegistry.notifyDisplayInfoChanged(0, INVALID_SUBSCRIPTION_ID, displayInfo);
@@ -1287,11 +1381,11 @@
         TelephonyDisplayInfo displayInfo = new TelephonyDisplayInfo(
                 TelephonyManager.NETWORK_TYPE_LTE,
                 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
-                false);
+                false, false, false);
         TelephonyDisplayInfo expectDisplayInfo = new TelephonyDisplayInfo(
                 TelephonyManager.NETWORK_TYPE_LTE,
                 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
-                false);
+                false, false, false);
 
         // Notify with invalid subId on default phone. Should NOT trigger callback.
         mTelephonyRegistry.notifyDisplayInfoChanged(0, INVALID_SUBSCRIPTION_ID, displayInfo);
@@ -1314,11 +1408,11 @@
         TelephonyDisplayInfo displayInfo = new TelephonyDisplayInfo(
                 TelephonyManager.NETWORK_TYPE_LTE,
                 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
-                false);
+                false, false, false);
         TelephonyDisplayInfo expectDisplayInfo = new TelephonyDisplayInfo(
                 TelephonyManager.NETWORK_TYPE_LTE,
                 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE,
-                false);
+                false, false, false);
         TelephonyCallback telephonyCallback2 = new TelephonyCallbackWrapper() {
             @Override
             public void onDisplayInfoChanged(TelephonyDisplayInfo displayInfoNotify) {
@@ -1589,6 +1683,57 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_EMERGENCY_CALLBACK_MODE_NOTIFICATION)
+    public void testNotifyCallbackModeStarted() {
+        final long durationMillis = 1000;
+        int[] events = {TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED};
+
+        mTelephonyRegistry.listenWithEventList(false, false, 1/*subId*/,
+                mContext.getOpPackageName(), mContext.getAttributionTag(),
+                mTelephonyCallback.callback, events, true);
+        mTelephonyRegistry.notifyCallbackModeStarted(0/*phoneId*/, 1/*subId*/,
+                TelephonyManager.EMERGENCY_CALLBACK_MODE_CALL, durationMillis);
+        processAllMessages();
+
+        assertEquals(1, mTelephonyCallback.invocationCount.get());
+        assertEquals(durationMillis, mCallbackModeDurationMillis);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_EMERGENCY_CALLBACK_MODE_NOTIFICATION)
+    public void testNotifyCallbackModeReStarted() {
+        final long durationMillis = 1000;
+        int[] events = {TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED};
+
+        mTelephonyRegistry.listenWithEventList(false, false, 1/*subId*/,
+                mContext.getOpPackageName(), mContext.getAttributionTag(),
+                mTelephonyCallback.callback, events, true);
+        mTelephonyRegistry.notifyCallbackModeRestarted(0/*phoneId*/, 1/*subId*/,
+                TelephonyManager.EMERGENCY_CALLBACK_MODE_CALL, durationMillis);
+        processAllMessages();
+
+        assertEquals(1, mTelephonyCallback.invocationCount.get());
+        assertEquals(durationMillis, mCallbackModeDurationMillis);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_EMERGENCY_CALLBACK_MODE_NOTIFICATION)
+    public void testNotifyCallbackModeStopped() {
+        final int reason = TelephonyManager.STOP_REASON_OUTGOING_EMERGENCY_CALL_INITIATED;
+        int[] events = {TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED};
+
+        mTelephonyRegistry.listenWithEventList(false, false, 1/*subId*/,
+                mContext.getOpPackageName(), mContext.getAttributionTag(),
+                mTelephonyCallback.callback, events, true);
+        mTelephonyRegistry.notifyCallbackModeStopped(0/*phoneId*/, 1/*subId*/,
+                TelephonyManager.EMERGENCY_CALLBACK_MODE_CALL, reason);
+        processAllMessages();
+
+        assertEquals(1, mTelephonyCallback.invocationCount.get());
+        assertEquals(reason, mCallbackModeStopReason);
+    }
+
+    @Test
     public void testNotifyCarrierRoamingNtnModeChanged() {
         int subId = INVALID_SUBSCRIPTION_ID;
         doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
@@ -1618,4 +1763,186 @@
         processAllMessages();
         assertTrue(mCarrierRoamingNtnEligible);
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public void testNotifyCarrierRoamingNtnAvailableServicesChanged() {
+        int subId = INVALID_SUBSCRIPTION_ID;
+        doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
+        doReturn(0/*slotIndex*/).when(mMockSubInfo).getSimSlotIndex();
+        int[] events = {TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED};
+
+        mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, true);
+
+        int[] services = {3, 6};
+        mTelephonyRegistry.notifyCarrierRoamingNtnAvailableServicesChanged(subId, services);
+        processAllMessages();
+        assertTrue(Arrays.equals(mCarrierRoamingNtnAvailableServices, services));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_PASS_COPIED_CALL_STATE_LIST)
+    public void testNotifyPreciseCallStateChangedInProcess() {
+        doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
+        doReturn(0/*slotIndex*/).when(mMockSubInfo).getSimSlotIndex();
+
+        final int subId = 1;
+        int[] events = {TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED};
+
+        mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, false);
+        processAllMessages();
+
+        int[] callState = {0, 5, 0};
+        String[] imsCallId = {"0", "1", "0"};
+        int[] imsServiceType = {0, 1, 0};
+        int[] imsCallType = {0, 1, 0};
+        int[] callState2 = {0, 1, 0};
+        mTelephonyRegistry.notifyPreciseCallState(
+                /*phoneId*/ 0, subId, callState, imsCallId, imsServiceType, imsCallType);
+        mTelephonyRegistry.notifyPreciseCallState(
+                /*phoneId*/ 0, subId, callState2, imsCallId, imsServiceType, imsCallType);
+        processAllMessages();
+
+        assertEquals(2, mCallStateList.size());
+        //make sure the call state is from the first report(callState).
+        assertEquals(5, mCallStateList.get(0).getFirst().getCallState());
+        //make sure the call state is from the second report(callState2).
+        assertEquals(1, mCallStateList.get(1).getFirst().getCallState());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public void testNotifyCarrierRoamingNtnSignalStrengthChanged() {
+        int subId = INVALID_SUBSCRIPTION_ID;
+        doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
+        doReturn(0/*slotIndex*/).when(mMockSubInfo).getSimSlotIndex();
+        int[] events = {TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED};
+
+        mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, true);
+
+        mTelephonyRegistry.notifyCarrierRoamingNtnSignalStrengthChanged(subId,
+                new NtnSignalStrength(NtnSignalStrength.NTN_SIGNAL_STRENGTH_GOOD));
+        processAllMessages();
+        assertEquals(mCarrierRoamingNtnSignalStrength.getLevel(),
+                NtnSignalStrength.NTN_SIGNAL_STRENGTH_GOOD);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
+    public void testNotifySatelliteStateChanged_onRegistration_getNotified() {
+        MySatelliteStateChangeListener listener = new MySatelliteStateChangeListener();
+        // Set initial satellite enabled state to true
+        mTelephonyRegistry.notifySatelliteStateChanged(true);
+
+        try {
+            // Start monitoring
+            mTelephonyRegistry.addSatelliteStateChangeListener(listener,
+                    mContext.getOpPackageName(), mContext.getAttributionTag());
+            processAllMessages();
+
+            // verify latest state is immediately available on registration
+            assertTrue(mIsSatelliteEnabled);
+        } finally {
+            // Clean up
+            mTelephonyRegistry.removeSatelliteStateChangeListener(listener,
+                    mContext.getOpPackageName());
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
+    public void testNotifySatelliteStateChanged_duringRegistration_getNotified() {
+        MySatelliteStateChangeListener listener = new MySatelliteStateChangeListener();
+        // Set initial satellite enabled state to true
+        mTelephonyRegistry.notifySatelliteStateChanged(true);
+
+        try {
+            // Start monitoring
+            mTelephonyRegistry.addSatelliteStateChangeListener(listener,
+                    mContext.getOpPackageName(), mContext.getAttributionTag());
+
+            // Satellite enabled state changed
+            mTelephonyRegistry.notifySatelliteStateChanged(false);
+            processAllMessages();
+            // We can receive the new state
+            assertFalse(mIsSatelliteEnabled);
+        } finally {
+            // Clean up
+            mTelephonyRegistry.removeSatelliteStateChangeListener(listener,
+                    mContext.getOpPackageName());
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
+    public void testNotifySatelliteStateChanged_removeRegistration_notNotified() {
+        MySatelliteStateChangeListener listener = new MySatelliteStateChangeListener();
+        // Set initial satellite enabled state to true
+        mTelephonyRegistry.notifySatelliteStateChanged(true);
+
+        try {
+            // Start monitoring
+            mTelephonyRegistry.addSatelliteStateChangeListener(listener,
+                    mContext.getOpPackageName(), mContext.getAttributionTag());
+            mTelephonyRegistry.notifySatelliteStateChanged(false);
+        } finally {
+            // Stop monitoring from now on
+            mTelephonyRegistry.removeSatelliteStateChangeListener(listener,
+                    mContext.getOpPackageName());
+        }
+
+        // Satellite enabled state changed again
+        mTelephonyRegistry.notifySatelliteStateChanged(true);
+        processAllMessages();
+        // We should not receive the new state change after monitoring end
+        assertFalse(mIsSatelliteEnabled);
+    }
+
+
+    @Test
+    @EnableFlags(Flags.FLAG_SECURITY_ALGORITHMS_UPDATE_INDICATIONS)
+    public void testNotifySecurityAlgorithmsChanged() {
+        int subId = 1;
+        int[] events = {TelephonyCallback.EVENT_SECURITY_ALGORITHMS_CHANGED};
+
+        mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, false);
+
+        SecurityAlgorithmUpdate update =
+                new SecurityAlgorithmUpdate(
+                        CONNECTION_EVENT_VOLTE_SIP, SECURITY_ALGORITHM_EEA2,
+                        SECURITY_ALGORITHM_HMAC_SHA1_96, false);
+
+        mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, false);
+        int invocationCount = mTelephonyCallback.invocationCount.get();
+        mTelephonyRegistry.notifySecurityAlgorithmsChanged(0, 1, update);
+        processAllMessages();
+        assertEquals(invocationCount + 1, mTelephonyCallback.invocationCount.get());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_CELLULAR_IDENTIFIER_DISCLOSURE_INDICATIONS)
+    public void testNotifyCellularIdentifierDisclosedChanged() {
+        int subId = 1;
+        int[] events = {TelephonyCallback.EVENT_CELLULAR_IDENTIFIER_DISCLOSED_CHANGED};
+
+        CellularIdentifierDisclosure disclosure =
+                new CellularIdentifierDisclosure(NAS_PROTOCOL_MESSAGE_ATTACH_REQUEST,
+                        CELLULAR_IDENTIFIER_IMSI,
+                        "001001",
+                        false);
+
+        mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, false);
+        int invocationCount = mTelephonyCallback.invocationCount.get();
+        mTelephonyRegistry.notifyCellularIdentifierDisclosedChanged(0, 1,
+                disclosure);
+        processAllMessages();
+        assertEquals(invocationCount + 1, mTelephonyCallback.invocationCount.get());
+    }
+
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index d80c9a2..0db881c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -747,6 +747,8 @@
         doReturn(mDataRetryManager).when(mDataNetworkController).getDataRetryManager();
         doReturn(mCarrierPrivilegesTracker).when(mPhone).getCarrierPrivilegesTracker();
         doReturn(0).when(mPhone).getPhoneId();
+        doReturn(true).when(mPhone).hasCalling();
+        doReturn(true).when(mPhone2).hasCalling();
 
         //mUiccController
         doReturn(mUiccCardApplication3gpp).when(mUiccController).getUiccCardApplication(anyInt(),
@@ -809,7 +811,7 @@
         doReturn(ServiceState.RIL_RADIO_TECHNOLOGY_LTE).when(mServiceState)
                 .getRilDataRadioTechnology();
         doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_LTE,
-                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false))
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false))
                 .when(mDisplayInfoController).getTelephonyDisplayInfo();
         doReturn(mPhone).when(mCT).getPhone();
         doReturn(mImsEcbm).when(mImsManager).getEcbmInterface();
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 0e2676e..0d6a668 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
@@ -101,9 +101,11 @@
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         mGoodTelephonyDisplayInfo = new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_NR,
-                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, false /*roaming*/);
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, false /*roaming*/,
+                false/*isNtn*/, false/*isSatelliteConstrainedDataStatus*/);
         mBadTelephonyDisplayInfo = new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UMTS,
-                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false /*roaming*/);
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false /*roaming*/,
+                false/*isNtn*/, false/*isSatelliteConstrainedDataStatus*/);
         mMockedPhoneSwitcherCallback =
                 mock(AutoDataSwitchController.AutoDataSwitchControllerCallback.class);
         mMockedAlarmManager = mock(AlarmManager.class);
@@ -140,7 +142,9 @@
                     .when(phone).isUserDataEnabled();
         }
         mDataEvaluation = new DataEvaluation(DataEvaluation.DataEvaluationReason.EXTERNAL_QUERY);
-        doReturn(mDataEvaluation).when(mDataNetworkController).getInternetEvaluation(anyBoolean());
+        doReturn(mDataEvaluation).when(mDataNetworkController).evaluateNetworkRequest(
+                any(TelephonyNetworkRequest.class),
+                eq(DataEvaluation.DataEvaluationReason.EXTERNAL_QUERY));
         doReturn(new int[]{SUB_1, SUB_2}).when(mSubscriptionManagerService)
                 .getActiveSubIdList(true);
         doAnswer(invocation -> {
@@ -165,6 +169,8 @@
                 .getAutoDataSwitchAvailabilityStabilityTimeThreshold();
         doReturn(120000L).when(mDataConfigManager)
                 .getAutoDataSwitchPerformanceStabilityTimeThreshold();
+        doReturn(150000L).when(mDataConfigManager)
+                .getAutoDataSwitchAvailabilitySwitchbackStabilityTimeThreshold();
         doReturn(MAX_RETRY).when(mDataConfigManager).getAutoDataSwitchValidationMaxRetry();
         doReturn(SCORE_TOLERANCE).when(mDataConfigManager).getAutoDataSwitchScoreTolerance();
         doAnswer(invocation -> {
@@ -250,7 +256,9 @@
         mDataEvaluation.addDataDisallowedReason(DataEvaluation.DataDisallowedReason
                 .NO_SUITABLE_DATA_PROFILE);
         doReturn(mDataEvaluation)
-                .when(mDataNetworkController).getInternetEvaluation(anyBoolean());
+                .when(mDataNetworkController).evaluateNetworkRequest(
+                        any(TelephonyNetworkRequest.class),
+                        eq(DataEvaluation.DataEvaluationReason.EXTERNAL_QUERY));
         mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED);
         processAllFutureMessages();
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java
index 95cefd8..88dce51 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java
@@ -126,20 +126,23 @@
         doReturn(SignalStrength.SIGNAL_STRENGTH_POOR).when(signalStrength).getLevel();
         assertThat(mDataConfigManagerUT.getAutoDataSwitchScore(new TelephonyDisplayInfo(
                         TelephonyManager.NETWORK_TYPE_LTE,
-                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, false/*isRoaming*/),
+                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED, false/*isRoaming*/,
+                        false/*isNtn*/, false/*isSatelliteConstrainedDataStatus*/),
                 signalStrength)).isEqualTo(10227);
         // Verify if entry contains any invalid negative scores, should yield 0.
         doReturn(SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN).when(signalStrength).getLevel();
         assertThat(mDataConfigManagerUT.getAutoDataSwitchScore(new TelephonyDisplayInfo(
                         TelephonyManager.NETWORK_TYPE_LTE,
-                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false/*isRoaming*/),
+                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false/*isRoaming*/,
+                        false/*isNtn*/, false/*isSatelliteConstrainedDataStatus*/),
                 signalStrength))
                 .isEqualTo(0/*OUT_OF_SERVICE_AUTO_DATA_SWITCH_SCORE*/);
         // Verify non-existent entry should yield -1
         doReturn(SignalStrength.SIGNAL_STRENGTH_POOR).when(signalStrength).getLevel();
         assertThat(mDataConfigManagerUT.getAutoDataSwitchScore(new TelephonyDisplayInfo(
                         TelephonyManager.NETWORK_TYPE_EDGE,
-                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false/*isRoaming*/),
+                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false/*isRoaming*/,
+                        false/*isNtn*/, false/*isSatelliteConstrainedDataStatus*/),
                 signalStrength))
                 .isEqualTo(0/*OUT_OF_SERVICE_AUTO_DATA_SWITCH_SCORE*/);
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
index ee713c6..3f2d6f2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -888,11 +888,8 @@
         doReturn(PhoneConstants.State.IDLE).when(mCT).getState();
         doReturn(new SubscriptionInfoInternal.Builder().setId(1).build())
                 .when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
-
         doReturn(true).when(mFeatureFlags).carrierEnabledSatelliteFlag();
         doReturn(true).when(mFeatureFlags).satelliteInternet();
-        doReturn(true).when(mFeatureFlags).simDisabledGracefulTearDown();
-
         when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
         doReturn(true).when(mMockPackageManager).hasSystemFeature(anyString());
 
@@ -2481,7 +2478,7 @@
 
         // Change data network type to NR
         doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_NR,
-                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false))
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false))
                 .when(mDisplayInfoController).getTelephonyDisplayInfo();
         dataNetwork.sendMessage(13/*EVENT_DISPLAY_INFO_CHANGED*/);
         processAllMessages();
@@ -2524,7 +2521,7 @@
 
         // Change data network type to NR
         doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_NR,
-                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false))
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false))
                 .when(mDisplayInfoController).getTelephonyDisplayInfo();
         dataNetwork.sendMessage(13/*EVENT_DISPLAY_INFO_CHANGED*/);
         processAllMessages();
@@ -2580,7 +2577,7 @@
 
         // Change data network type to NR
         doReturn(new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_NR,
-                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false))
+                TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false))
                 .when(mDisplayInfoController).getTelephonyDisplayInfo();
         dataNetwork.sendMessage(13/*EVENT_DISPLAY_INFO_CHANGED*/);
         processAllMessages();
@@ -4311,7 +4308,7 @@
     }
 
     @Test
-    public void testImsGracefulTearDownSimRemoval() throws Exception {
+    public void testImsGracefulTearDown() throws Exception {
         setImsRegistered(true);
         setRcsRegistered(true);
 
@@ -4357,52 +4354,6 @@
     }
 
     @Test
-    public void testImsGracefulTearDownSimDisabled() throws Exception {
-        setImsRegistered(true);
-        setRcsRegistered(true);
-
-        NetworkCapabilities netCaps = new NetworkCapabilities();
-        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
-        netCaps.maybeMarkCapabilitiesRestricted();
-        netCaps.setRequestorPackageName(FAKE_MMTEL_PACKAGE);
-
-        NetworkRequest nativeNetworkRequest = new NetworkRequest(netCaps,
-                ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId, NetworkRequest.Type.REQUEST);
-        TelephonyNetworkRequest networkRequest = new TelephonyNetworkRequest(
-                nativeNetworkRequest, mPhone, mFeatureFlags);
-
-        mDataNetworkControllerUT.addNetworkRequest(networkRequest);
-
-        processAllMessages();
-        Mockito.clearInvocations(mPhone);
-
-        // SIM disabled
-        mDataNetworkControllerUT.obtainMessage(9/*EVENT_SIM_STATE_CHANGED*/,
-                TelephonyManager.SIM_STATE_NOT_READY, 0).sendToTarget();
-        processAllMessages();
-
-        // Make sure data network enters disconnecting state
-        ArgumentCaptor<PreciseDataConnectionState> pdcsCaptor =
-                ArgumentCaptor.forClass(PreciseDataConnectionState.class);
-        verify(mPhone).notifyDataConnection(pdcsCaptor.capture());
-        PreciseDataConnectionState pdcs = pdcsCaptor.getValue();
-        assertThat(pdcs.getState()).isEqualTo(TelephonyManager.DATA_DISCONNECTING);
-
-        // IMS de-registered. Now data network is safe to be torn down.
-        Mockito.clearInvocations(mPhone);
-        setImsRegistered(false);
-        setRcsRegistered(false);
-        processAllMessages();
-
-        // All data should be disconnected.
-        verifyAllDataDisconnected();
-        verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
-        verify(mPhone).notifyDataConnection(pdcsCaptor.capture());
-        pdcs = pdcsCaptor.getValue();
-        assertThat(pdcs.getState()).isEqualTo(TelephonyManager.DATA_DISCONNECTED);
-    }
-
-    @Test
     public void testNoGracefulTearDownForEmergencyDataNetwork() throws Exception {
         setImsRegistered(true);
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
index f7990b9..6e41448 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
@@ -146,7 +146,7 @@
     private AutoDataSwitchController.AutoDataSwitchControllerCallback mAutoDataSwitchCallback;
     private TelephonyDisplayInfo mTelephonyDisplayInfo = new TelephonyDisplayInfo(
             TelephonyManager.NETWORK_TYPE_NR,
-            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
+            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false, false, false);
     private SubscriptionManagerServiceCallback mSubscriptionManagerServiceCallback;
 
     @Before
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
index 2f3fabf..ec7ad3c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
@@ -16,6 +16,7 @@
 package com.android.internal.telephony.domainselection;
 
 import static android.telephony.AccessNetworkConstants.AccessNetworkType.EUTRAN;
+import static android.telephony.AccessNetworkConstants.AccessNetworkType.UNKNOWN;
 import static android.telephony.AccessNetworkConstants.AccessNetworkType.UTRAN;
 import static android.telephony.DomainSelectionService.SCAN_TYPE_FULL_SERVICE;
 import static android.telephony.DomainSelectionService.SCAN_TYPE_NO_PREFERENCE;
@@ -516,6 +517,131 @@
 
     @Test
     @SmallTest
+    public void testWwanSelectorCallbackOnRequestEmergencyNetworkScanAndErrorForScanTypeFull()
+            throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, TELECOM_CALL_ID1, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        IWwanSelectorCallback wwanCallback = onWwanSelected(transportCallback);
+
+        assertNotNull(wwanCallback);
+
+        int[] preferredNetworks = new int[] { EUTRAN, UTRAN };
+        int scanType = SCAN_TYPE_FULL_SERVICE;
+        IWwanSelectorResultCallback resultCallback =
+                Mockito.mock(IWwanSelectorResultCallback.class);
+
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                false, resultCallback);
+        processAllMessages();
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
+
+        verify(mPhone).registerForEmergencyNetworkScan(handlerCaptor.capture(), anyInt(), any());
+        verify(mCi).registerForModemReset(any(), anyInt(), any());
+        processAllMessages();
+
+        int[] expectedPreferredNetworks = new int[] { EUTRAN, UTRAN };
+
+        verify(mPhone).triggerEmergencyNetworkScan(eq(expectedPreferredNetworks),
+                eq(scanType), msgCaptor.capture());
+
+        Handler handler = handlerCaptor.getValue();
+        Message msg = msgCaptor.getValue();
+
+        assertNotNull(handler);
+        assertNotNull(msg);
+
+        CommandException commandException =
+                new CommandException(CommandException.Error.INTERNAL_ERR);
+        handler.sendMessage(handler.obtainMessage(msg.what,
+                new AsyncResult((Integer) scanType, null, commandException)));
+        processAllMessages();
+
+        EmergencyRegistrationResult regResult = new EmergencyRegistrationResult(UNKNOWN,
+                NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN,
+                NetworkRegistrationInfo.DOMAIN_UNKNOWN, false, false, 0, 0, "", "", "");
+        verify(resultCallback).onComplete(eq(regResult));
+        verify(mPhone, times(0)).cancelEmergencyNetworkScan(anyBoolean(), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testWwanSelectorCallbackOnRequestEmergencyNetworkScanAndErrorForScanTypeNoPref()
+            throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_CALLING, true,
+                false, 0, TELECOM_CALL_ID1, null, null, null);
+
+        mDsc.selectDomain(attr);
+
+        IDomainSelector domainSelector = Mockito.mock(IDomainSelector.class);
+        transportCallback.onCreated(domainSelector);
+
+        IWwanSelectorCallback wwanCallback = onWwanSelected(transportCallback);
+
+        assertNotNull(wwanCallback);
+
+        int[] preferredNetworks = new int[] { EUTRAN, UTRAN };
+        int scanType = SCAN_TYPE_NO_PREFERENCE;
+        IWwanSelectorResultCallback resultCallback =
+                Mockito.mock(IWwanSelectorResultCallback.class);
+
+        wwanCallback.onRequestEmergencyNetworkScan(preferredNetworks, scanType,
+                false, resultCallback);
+        processAllMessages();
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
+
+        verify(mPhone).registerForEmergencyNetworkScan(handlerCaptor.capture(), anyInt(), any());
+        verify(mCi).registerForModemReset(any(), anyInt(), any());
+        processAllMessages();
+
+        int[] expectedPreferredNetworks = new int[] { EUTRAN, UTRAN };
+
+        verify(mPhone).triggerEmergencyNetworkScan(eq(expectedPreferredNetworks),
+                eq(scanType), msgCaptor.capture());
+
+        Handler handler = handlerCaptor.getValue();
+        Message msg = msgCaptor.getValue();
+
+        assertNotNull(handler);
+        assertNotNull(msg);
+
+        CommandException commandException =
+                new CommandException(CommandException.Error.INTERNAL_ERR);
+        handler.sendMessage(handler.obtainMessage(msg.what,
+                new AsyncResult((Integer) scanType, null, commandException)));
+        processAllMessages();
+
+        verify(resultCallback, times(0)).onComplete(any());
+        verify(mPhone, times(0)).cancelEmergencyNetworkScan(anyBoolean(), any());
+    }
+
+    @Test
+    @SmallTest
     public void testModemResetOnRequestEmergencyNetworkDuringNetworkScan()
             throws Exception {
         mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
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 25e9715..9253fbf 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
@@ -21,11 +21,12 @@
 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 android.telephony.TelephonyManager.EMERGENCY_CALLBACK_MODE_CALL;
+import static android.telephony.TelephonyManager.EMERGENCY_CALLBACK_MODE_SMS;
 
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_CALLBACK;
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WLAN;
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN;
-import static com.android.internal.telephony.emergency.EmergencyStateTracker.DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -36,6 +37,7 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.anyVararg;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
@@ -103,6 +105,7 @@
     private static final String TEST_SMS_ID = "1111";
     private static final String TEST_SMS_ID_2 = "2222";
     private static final int TEST_ECM_EXIT_TIMEOUT_MS = 500;
+    private static final int TEST_WAIT_FOR_IN_SERVICE_TIMEOUT_MS = 3000;
     private static final EmergencyRegistrationResult E_REG_RESULT = new EmergencyRegistrationResult(
             EUTRAN, REGISTRATION_STATE_HOME, DOMAIN_CS_PS, true, true, 0, 1, "001", "01", "US");
 
@@ -121,6 +124,7 @@
 
         doReturn(TelephonyManager.SIM_STATE_READY)
                 .when(mTelephonyManagerProxy).getSimState(anyInt());
+        doReturn(true).when(mFeatureFlags).emergencyCallbackModeNotification();
     }
 
     @After
@@ -135,7 +139,8 @@
             EmergencyStateTracker.getInstance();
         });
 
-        EmergencyStateTracker.make(mContext, true);
+        EmergencyStateTracker
+                .make(mContext, true, TEST_WAIT_FOR_IN_SERVICE_TIMEOUT_MS, mFeatureFlags);
 
         assertNotNull(EmergencyStateTracker.getInstance());
     }
@@ -143,7 +148,8 @@
     @Test
     @SmallTest
     public void getInstance_returnsSameInstance() {
-        EmergencyStateTracker.make(mContext, true);
+        EmergencyStateTracker
+                .make(mContext, true, TEST_WAIT_FOR_IN_SERVICE_TIMEOUT_MS, mFeatureFlags);
         EmergencyStateTracker instance1 = EmergencyStateTracker.getInstance();
         EmergencyStateTracker instance2 = EmergencyStateTracker.getInstance();
 
@@ -180,7 +186,7 @@
         ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
                 .forClass(RadioOnStateListener.Callback.class);
         verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
-                eq(false), eq(DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS), eq(false));
+                eq(false), eq(TEST_WAIT_FOR_IN_SERVICE_TIMEOUT_MS));
         // isOkToCall() should return true when IN_SERVICE state
         assertFalse(callback.getValue()
                 .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
@@ -239,7 +245,7 @@
         ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
                 .forClass(RadioOnStateListener.Callback.class);
         verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
-                eq(false), eq(DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS), eq(false));
+                eq(false), eq(TEST_WAIT_FOR_IN_SERVICE_TIMEOUT_MS));
         // onTimeout should return true when radion on
         assertFalse(callback.getValue()
                 .isOkToCall(testPhone, ServiceState.STATE_OUT_OF_SERVICE, false));
@@ -266,6 +272,7 @@
     @Test
     @SmallTest
     public void startEmergencyCall_radioOff_turnOnRadioHangupCallTurnOffRadio() {
+        android.telecom.Connection testConnection = new android.telecom.Connection() {};
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 true /* isSuplDdsSwitchRequiredForEmergencyCall */);
         // Create test Phones and set radio off
@@ -281,16 +288,17 @@
                 .build();
         doReturn(nri).when(ss).getNetworkRegistrationInfo(anyInt(), anyInt());
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(testPhone,
-                mTestConnection1, false);
+                testConnection, false);
 
         // startEmergencyCall should trigger radio on
         ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
                 .forClass(RadioOnStateListener.Callback.class);
         verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
-                eq(false), eq(DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS), eq(false));
+                eq(false), eq(TEST_WAIT_FOR_IN_SERVICE_TIMEOUT_MS));
 
         // Hangup the call
-        emergencyStateTracker.endCall(mTestConnection1);
+        testConnection.setDisconnected(null);
+        emergencyStateTracker.endCall(testConnection);
 
         // onTimeout and isOkToCall should return true even in case radion is off
         assertTrue(callback.getValue()
@@ -301,6 +309,7 @@
         callback.getValue().onComplete(null, true);
 
         assertFalse(future.isDone());
+        verify(testPhone).setRadioPower(true, false, false, true);
     }
 
     /**
@@ -323,7 +332,7 @@
         ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
                 .forClass(RadioOnStateListener.Callback.class);
         verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
-                eq(false), eq(DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS), eq(false));
+                eq(false), eq(TEST_WAIT_FOR_IN_SERVICE_TIMEOUT_MS));
         // Verify future completes with DisconnectCause.POWER_OFF if radio not ready
         CompletableFuture<Void> unused = future.thenAccept((result) -> {
             assertEquals((Integer) result, (Integer) DisconnectCause.POWER_OFF);
@@ -346,7 +355,7 @@
                 true /* isRadioOn */);
         when(mSST.isRadioOn()).thenReturn(true);
         // Satellite enabled
-        when(mSatelliteController.isSatelliteEnabled()).thenReturn(true);
+        when(mSatelliteController.isSatelliteEnabledOrBeingEnabled()).thenReturn(true);
 
         setConfigForDdsSwitch(testPhone, null,
                 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "150");
@@ -359,11 +368,11 @@
         ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
                 .forClass(RadioOnStateListener.Callback.class);
         verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
-                eq(false), eq(0), eq(false));
+                eq(false), eq(0));
         // isOkToCall() should return true once satellite modem is off
         assertFalse(callback.getValue()
                 .isOkToCall(testPhone, ServiceState.STATE_IN_SERVICE, false));
-        when(mSatelliteController.isSatelliteEnabled()).thenReturn(false);
+        when(mSatelliteController.isSatelliteEnabledOrBeingEnabled()).thenReturn(false);
         assertTrue(callback.getValue()
                 .isOkToCall(testPhone, ServiceState.STATE_IN_SERVICE, false));
         // Once radio on is complete, trigger delay dial
@@ -391,7 +400,7 @@
         Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
                 true /* isRadioOn */);
         // Satellite enabled
-        when(mSatelliteController.isSatelliteEnabled()).thenReturn(true);
+        when(mSatelliteController.isSatelliteEnabledOrBeingEnabled()).thenReturn(true);
 
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(testPhone,
                 mTestConnection1, false);
@@ -400,7 +409,7 @@
         ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
                 .forClass(RadioOnStateListener.Callback.class);
         verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
-                eq(false), eq(0), eq(false));
+                eq(false), eq(0));
         // Verify future completes with DisconnectCause.POWER_OFF if radio not ready
         CompletableFuture<Void> unused = future.thenAccept((result) -> {
             assertEquals((Integer) result, (Integer) DisconnectCause.SATELLITE_ENABLED);
@@ -428,7 +437,7 @@
 
         // Radio already on so shouldn't trigger this
         verify(mRadioOnHelper, never()).triggerRadioOnAndListen(any(), anyBoolean(), any(),
-                anyBoolean(), eq(0), eq(false));
+                anyBoolean(), eq(0));
         // Carrier supports control-plane fallback, so no DDS switch
         verify(mPhoneSwitcher, never()).overrideDefaultDataForEmergency(anyInt(), anyInt(), any());
     }
@@ -761,6 +770,8 @@
                 .getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, true));
         // Verify emergency callback mode set on modem
         verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any());
+        // Verify emergency callback mode started with the correct emergency type
+        verify(testPhone).startEmergencyCallbackMode(eq(EMERGENCY_CALLBACK_MODE_CALL), anyLong());
     }
 
     /**
@@ -819,6 +830,9 @@
 
         processAllFutureMessages();
 
+        // Verify emergency callback mode stopped with the correct emergency type and reason.
+        verify(testPhone).stopEmergencyCallbackMode(eq(EmergencyStateTracker.EMERGENCY_TYPE_CALL),
+                eq(TelephonyManager.STOP_REASON_TIMER_EXPIRED));
         // Verify exitEmergencyMode() is called after timeout
         verify(testPhone).exitEmergencyMode(any(Message.class));
         assertFalse(emergencyStateTracker.isInEmergencyMode());
@@ -854,6 +868,9 @@
 
         emergencyStateTracker.onCellularRadioPowerOffRequested();
 
+        // Verify emergency callback mode stopped with the correct emergency type and reason.
+        verify(testPhone).stopEmergencyCallbackMode(eq(EmergencyStateTracker.EMERGENCY_TYPE_CALL),
+                eq(TelephonyManager.STOP_REASON_UNKNOWN));
         // Verify exitEmergencyMode() is called.
         verify(testPhone).exitEmergencyMode(any(Message.class));
         assertFalse(emergencyStateTracker.isInEcm());
@@ -1005,7 +1022,8 @@
         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));
-
+        verify(phone0, times(1)).startEmergencyCallbackMode(
+                eq(EMERGENCY_CALLBACK_MODE_CALL), anyLong());
         // Second emergency call started.
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
                 mTestConnection2, false);
@@ -1013,6 +1031,8 @@
         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));
+        verify(phone0, times(1)).startEmergencyCallbackMode(
+                eq(EMERGENCY_CALLBACK_MODE_CALL), anyLong());
     }
 
     @Test
@@ -1087,6 +1107,10 @@
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WLAN), any(Message.class));
         verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
         verify(phone0, never()).exitEmergencyMode(any(Message.class));
+        verify(phone0, times(2)).startEmergencyCallbackMode(
+                eq(EMERGENCY_CALLBACK_MODE_CALL), anyLong());
+        verify(phone0, times(1)).stopEmergencyCallbackMode(
+                eq(EMERGENCY_CALLBACK_MODE_CALL), anyInt());
     }
 
     @Test
@@ -1224,6 +1248,7 @@
         verify(mCarrierConfigManager).getConfigForSubId(anyInt(),
                 eq(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT));
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        verify(phone0).startEmergencyCallbackMode(eq(EMERGENCY_CALLBACK_MODE_SMS), anyLong());
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInScbm());
     }
@@ -1256,6 +1281,7 @@
         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));
+        verify(phone0).startEmergencyCallbackMode(eq(EMERGENCY_CALLBACK_MODE_SMS), anyLong());
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInScbm());
     }
@@ -1288,6 +1314,8 @@
         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));
+        verify(phone0).startEmergencyCallbackMode(eq(EMERGENCY_CALLBACK_MODE_SMS), anyLong());
+        verify(phone0).restartEmergencyCallbackMode(eq(EMERGENCY_CALLBACK_MODE_SMS), anyLong());
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInScbm());
     }
@@ -1413,6 +1441,7 @@
         processAllMessages();
 
         verify(phone0, times(2)).setEmergencyMode(anyInt(), any(Message.class));
+        verify(phone0).startEmergencyCallbackMode(eq(EMERGENCY_CALLBACK_MODE_CALL), anyLong());
         // Expect: DisconnectCause#NOT_DISCONNECTED.
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
@@ -1473,6 +1502,8 @@
         CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone1,
                 TEST_SMS_ID_2, false);
 
+        verify(phone0).stopEmergencyCallbackMode(eq(EMERGENCY_CALLBACK_MODE_SMS),
+                eq(TelephonyManager.STOP_REASON_EMERGENCY_SMS_SENT));
         verify(phone0).exitEmergencyMode(any(Message.class));
         // Waits for exiting emergency mode on other phone.
         assertFalse(future.isDone());
@@ -1726,6 +1757,7 @@
         assertFalse(emergencyStateTracker.isInEmergencyCall());
         assertTrue(emergencyStateTracker.isInScbm());
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        verify(phone0).startEmergencyCallbackMode(eq(EMERGENCY_CALLBACK_MODE_SMS), anyLong());
     }
 
     @Test
@@ -2156,6 +2188,7 @@
         processAllMessages();
 
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        verify(phone0).startEmergencyCallbackMode(eq(EMERGENCY_CALLBACK_MODE_CALL), anyLong());
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInEmergencyCall());
@@ -2212,6 +2245,8 @@
 
         // Enter emergency callback mode and emergency mode changed by SMS end.
         verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+        verify(phone0).startEmergencyCallbackMode(eq(EMERGENCY_CALLBACK_MODE_CALL), anyLong());
+        verify(phone0).startEmergencyCallbackMode(eq(EMERGENCY_CALLBACK_MODE_SMS), anyLong());
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInEmergencyCall());
@@ -3168,7 +3203,7 @@
         // Wait for the radio off for all phones
         verify(mSST, times(2)).registerForVoiceRegStateOrRatChanged(any(), anyInt(), any());
         verify(mRadioOnHelper, never()).triggerRadioOnAndListen(any(), anyBoolean(), any(),
-                anyBoolean(), eq(0), eq(false));
+                anyBoolean(), eq(0));
     }
 
     /**
@@ -3378,8 +3413,9 @@
         doNothing().when(mPhoneSwitcher).overrideDefaultDataForEmergency(
                 anyInt(), anyInt(), any());
         return new EmergencyStateTracker(mContext, mTestableLooper.getLooper(),
-                isSuplDdsSwitchRequiredForEmergencyCall, mPhoneFactoryProxy, mPhoneSwitcherProxy,
-                mTelephonyManagerProxy, mRadioOnHelper, TEST_ECM_EXIT_TIMEOUT_MS);
+                isSuplDdsSwitchRequiredForEmergencyCall, TEST_WAIT_FOR_IN_SERVICE_TIMEOUT_MS,
+                mPhoneFactoryProxy, mPhoneSwitcherProxy, mTelephonyManagerProxy, mRadioOnHelper,
+                TEST_ECM_EXIT_TIMEOUT_MS, mFeatureFlags);
     }
 
     private Phone setupTestPhoneForEmergencyCall(boolean isRoaming, boolean isRadioOn) {
@@ -3483,6 +3519,7 @@
                 eq(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT));
         if (!emergencyStateTracker.isInEcm() && !emergencyStateTracker.isInEmergencyCall()) {
             verify(phone).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
+            verify(phone).startEmergencyCallbackMode(eq(EMERGENCY_CALLBACK_MODE_SMS), anyLong());
         }
         assertTrue(emergencyStateTracker.isInEmergencyMode());
         assertTrue(emergencyStateTracker.isInScbm());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java b/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java
index bb349a7..e9bff91 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java
@@ -104,7 +104,7 @@
      */
     @Test
     public void testRegisterForSatelliteCallback() {
-        doReturn(true).when(mSatelliteController).isSatelliteEnabled();
+        doReturn(true).when(mSatelliteController).isSatelliteEnabledOrBeingEnabled();
         mListener.waitForRadioOn(mMockPhone, mCallback, false, false, 0);
 
         waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
@@ -229,7 +229,7 @@
 
     @Test
     public void testTimeout_RetryFailure_WithSatellite() {
-        doReturn(true).when(mSatelliteController).isSatelliteEnabled();
+        doReturn(true).when(mSatelliteController).isSatelliteEnabledOrBeingEnabled();
         ServiceState state = new ServiceState();
         state.setState(ServiceState.STATE_POWER_OFF);
         when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
index 879118f..569785b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
@@ -1906,6 +1906,8 @@
 
         when(mSubscriptionManager.canManageSubscription(subInfo, PACKAGE_NAME)).thenReturn(
                 hasPrivileges);
+        when(mSubscriptionManager.canManageSubscriptionAsUser(eq(subInfo), eq(PACKAGE_NAME), any()))
+                .thenReturn(hasPrivileges);
         when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(
                 Collections.singletonList(subInfo));
     }
@@ -1943,8 +1945,12 @@
                 .build();
         when(mSubscriptionManager.canManageSubscription(subInfo1, PACKAGE_NAME)).thenReturn(
                 hasPrivileges);
+        when(mSubscriptionManager.canManageSubscriptionAsUser(eq(subInfo1), eq(PACKAGE_NAME),
+                any())).thenReturn(hasPrivileges);
         when(mSubscriptionManager.canManageSubscription(subInfo2, PACKAGE_NAME)).thenReturn(
                 hasPrivileges);
+        when(mSubscriptionManager.canManageSubscriptionAsUser(eq(subInfo2), eq(PACKAGE_NAME),
+                any())).thenReturn(hasPrivileges);
         ArrayList<SubscriptionInfo> subInfos = new ArrayList<>(Arrays.asList(subInfo1, subInfo2));
         when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(subInfos);
     }
@@ -1963,8 +1969,12 @@
                 .build();
         when(mSubscriptionManager.canManageSubscription(subInfo1, PACKAGE_NAME)).thenReturn(
                 false);
+        when(mSubscriptionManager.canManageSubscriptionAsUser(eq(subInfo1), eq(PACKAGE_NAME),
+                any())).thenReturn(false);
         when(mSubscriptionManager.canManageSubscription(subInfo2, PACKAGE_NAME)).thenReturn(
                 true);
+        when(mSubscriptionManager.canManageSubscriptionAsUser(eq(subInfo2), eq(PACKAGE_NAME),
+                any())).thenReturn(true);
         ArrayList<SubscriptionInfo> subInfos = new ArrayList<>(Arrays.asList(subInfo1, subInfo2));
         when(mSubscriptionManager.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(subInfos);
     }
@@ -1979,6 +1989,8 @@
                 .build();
         when(mSubscriptionManager.canManageSubscription(subInfo, PACKAGE_NAME)).thenReturn(
                 hasPrivileges);
+        when(mSubscriptionManager.canManageSubscriptionAsUser(eq(subInfo), eq(PACKAGE_NAME), any()))
+                .thenReturn(hasPrivileges);
         when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
                 Collections.singletonList(subInfo));
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccSessionTest.java b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccSessionTest.java
index f91088c..6e0d383 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccSessionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccSessionTest.java
@@ -103,6 +103,7 @@
 
     private EuiccSession mEuiccSession;
     @Mock private ApduSender mApduSender;
+    @Mock private ApduSender mApduSender2;
 
     @Before
     public void setUp() throws Exception {
@@ -114,6 +115,7 @@
     public void startOneSession_featureDisabled_noop() throws Exception {
         mEuiccSession.startSession(SESSION_ID_1);
         mEuiccSession.noteChannelOpen(mApduSender);
+        mEuiccSession.noteChannelOpen(mApduSender2);
 
         assertThat(mEuiccSession.hasSession()).isFalse();
 
@@ -121,6 +123,7 @@
 
         assertThat(mEuiccSession.hasSession()).isFalse();
         verify(mApduSender, never()).closeAnyOpenChannel();
+        verify(mApduSender2, never()).closeAnyOpenChannel();
     }
 
     @Test
@@ -128,6 +131,7 @@
     public void startOneSession_endSession_hasSession() throws Exception {
         mEuiccSession.startSession(SESSION_ID_1);
         mEuiccSession.noteChannelOpen(mApduSender);
+        mEuiccSession.noteChannelOpen(mApduSender2);
 
         assertThat(mEuiccSession.hasSession()).isTrue();
 
@@ -140,6 +144,7 @@
 
         assertThat(mEuiccSession.hasSession()).isFalse();
         verify(mApduSender).closeAnyOpenChannel();
+        verify(mApduSender2).closeAnyOpenChannel();
     }
 
     @Test
@@ -164,7 +169,24 @@
 
     @Test
     @EnableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER)
-    public void noteChannelOpen_noSession_noop() throws Exception {
+    public void startTwoSessions_endAllSessions_hasSession() throws Exception {
+        mEuiccSession.startSession(SESSION_ID_1);
+        mEuiccSession.noteChannelOpen(mApduSender);
+        mEuiccSession.startSession(SESSION_ID_2);
+        mEuiccSession.noteChannelOpen(mApduSender2);
+
+        assertThat(mEuiccSession.hasSession()).isTrue();
+
+        mEuiccSession.endAllSessions();
+
+        assertThat(mEuiccSession.hasSession()).isFalse();
+        verify(mApduSender).closeAnyOpenChannel();
+        verify(mApduSender2).closeAnyOpenChannel();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER)
+    public void noteChannelOpen_noSession_endSession_noop() throws Exception {
         // noteChannelOpen called without a session started
         mEuiccSession.noteChannelOpen(mApduSender);
 
@@ -175,4 +197,18 @@
         assertThat(mEuiccSession.hasSession()).isFalse();
         verify(mApduSender, never()).closeAnyOpenChannel();
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER)
+    public void endAllSessions_noSession_endAllSessions_noOp() throws Exception {
+        // noteChannelOpen called without a session started
+        mEuiccSession.noteChannelOpen(mApduSender);
+
+        assertThat(mEuiccSession.hasSession()).isFalse();
+
+        mEuiccSession.endAllSessions();
+
+        assertThat(mEuiccSession.hasSession()).isFalse();
+        verify(mApduSender, never()).closeAnyOpenChannel();
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
index 8a10fde..a31be59 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
@@ -23,6 +23,7 @@
 import static com.android.internal.telephony.TelephonyTestUtils.waitForMs;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
@@ -45,6 +46,7 @@
 import android.content.pm.ServiceInfo;
 import android.location.Country;
 import android.location.CountryDetector;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.Message;
@@ -54,6 +56,7 @@
 import android.service.carrier.CarrierMessagingService;
 import android.service.carrier.ICarrierMessagingCallback;
 import android.service.carrier.ICarrierMessagingService;
+import android.telephony.ServiceState;
 import android.telephony.SmsManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -63,6 +66,7 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
 import com.android.internal.telephony.ContextFixture;
 import com.android.internal.telephony.ISub;
 import com.android.internal.telephony.SMSDispatcher;
@@ -70,6 +74,7 @@
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.TelephonyTestUtils;
 import com.android.internal.telephony.TestApplication;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.uicc.IsimUiccRecords;
 
@@ -313,7 +318,7 @@
         // send SMS and check sentIntent
         mReceivedTestIntent = false;
         mGsmSmsDispatcher.sendMultipartText("+123" /*destAddr*/, "222" /*scAddr*/, parts,
-                sentIntents, null, null, null, mCallingUserId, false, -1, false, -1, 0L);
+                sentIntents, null, null, null, mCallingUserId, false, -1, false, -1, 0L, 0L);
 
         waitForMs(500);
         synchronized (mLock) {
@@ -377,23 +382,35 @@
                 any(ICarrierMessagingCallback.class));
     }
 
-    @Test
-    @SmallTest
-    @Ignore("b/256282780")
-    public void testSendSmsByCarrierApp() throws Exception {
+    private int sendSmsWithCarrierAppResponse(int carrierAppResultCode) throws Exception {
         mockCarrierApp();
-        mockCarrierAppStubResults(CarrierMessagingService.SEND_STATUS_OK,
-                mICarrierAppMessagingService, true);
+        mockCarrierAppStubResults(carrierAppResultCode, mICarrierAppMessagingService, true);
         registerTestIntentReceiver();
 
-        PendingIntent pendingIntent = PendingIntent.getBroadcast(TestApplication.getAppContext(), 0,
-                new Intent(TEST_INTENT)
-                        .setPackage(TestApplication.getAppContext().getPackageName()),
-                PendingIntent.FLAG_MUTABLE);
+        PendingIntent pendingIntent =
+                PendingIntent.getBroadcast(
+                        TestApplication.getAppContext(),
+                        0,
+                        new Intent(TEST_INTENT)
+                                .setPackage(TestApplication.getAppContext().getPackageName()),
+                        PendingIntent.FLAG_MUTABLE);
         mReceivedTestIntent = false;
 
-        mGsmSmsDispatcher.sendText("6501002000", "121" /*scAddr*/, "test sms",
-                pendingIntent, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
+        mGsmSmsDispatcher.sendText(
+                "6501002000",
+                "121" /*scAddr*/,
+                "test sms",
+                pendingIntent,
+                null,
+                null,
+                null,
+                mCallingUserId,
+                false,
+                -1,
+                false,
+                -1,
+                false,
+                0L);
         processAllMessages();
         synchronized (mLock) {
             if (!mReceivedTestIntent) {
@@ -402,15 +419,48 @@
             }
             assertEquals(true, mReceivedTestIntent);
             int resultCode = mTestReceiver.getResultCode();
-            assertTrue("Unexpected result code: " + resultCode,
-                    resultCode == SmsManager.RESULT_ERROR_NONE || resultCode == Activity.RESULT_OK);
-            verify(mSimulatedCommandsVerifier, times(0)).sendSMS(anyString(), anyString(),
-                    any(Message.class));
+            verify(mSimulatedCommandsVerifier, times(0))
+                    .sendSMS(anyString(), anyString(), any(Message.class));
+            return resultCode;
         }
     }
 
     @Test
     @SmallTest
+    @Ignore("b/256282780")
+    public void testSendSmsByCarrierApp() throws Exception {
+        int resultCode = sendSmsWithCarrierAppResponse(CarrierMessagingService.SEND_STATUS_OK);
+        assertTrue(
+                "Unexpected result code: " + resultCode,
+                resultCode == SmsManager.RESULT_ERROR_NONE || resultCode == Activity.RESULT_OK);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendSmsByCarrierApp_PermanentFailure() throws Exception {
+        int resultCode = sendSmsWithCarrierAppResponse(CarrierMessagingService.SEND_STATUS_ERROR);
+        assertTrue(
+                "Unexpected result code: " + resultCode,
+                resultCode == SmsManager.RESULT_RIL_GENERIC_ERROR);
+    }
+
+    @Test
+    @SmallTest
+    public void testSendSmsByCarrierApp_FailureWithReason() throws Exception {
+        if (!Flags.temporaryFailuresInCarrierMessagingService()) {
+            return;
+        }
+        doReturn(true).when(mFeatureFlags).temporaryFailuresInCarrierMessagingService();
+        int resultCode =
+                sendSmsWithCarrierAppResponse(
+                        CarrierMessagingService.SEND_STATUS_RESULT_ERROR_NO_SERVICE);
+        assertTrue(
+                "Unexpected result code: " + resultCode,
+                resultCode == SmsManager.RESULT_ERROR_NO_SERVICE);
+    }
+
+    @Test
+    @SmallTest
     public void testSendSmsByCarrierAppNoResponse() throws Exception {
         mockCarrierApp();
         // do not mock result, instead reduce the timeout for test
@@ -456,7 +506,7 @@
 
         mGsmSmsDispatcher.sendMultipartText("6501002000" /*destAddr*/, "222" /*scAddr*/, parts,
                 withSentIntents ? sentIntents : null, null, null, null, mCallingUserId,
-                false, -1, false, -1, 0L);
+                false, -1, false, -1, 0L, 0L);
     }
 
     @Test
@@ -549,7 +599,7 @@
             messageRef += parts.size();
         }
         mGsmSmsDispatcher.sendMultipartText("6501002000" /*destAddr*/, "222" /*scAddr*/, parts,
-                null, null, null, null, mCallingUserId, false, -1, false, -1, 0L);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, 0L, 0L);
         waitForMs(150);
         ArgumentCaptor<String> pduCaptor = ArgumentCaptor.forClass(String.class);
 
@@ -597,4 +647,53 @@
         byte[] pdu = IccUtils.hexStringToBytes(pduCaptor.getValue());
         assertEquals(0, pdu[1]);
     }
+
+    @Test
+    public void testSendRawPdu_isMtSmsPollingMessage_doesNotCallOnFailed() throws Exception {
+        setupMockPackagePermissionChecks();
+        mContextFixture.addCallingOrSelfPermission(ContextFixture.PERMISSION_ENABLE_ALL);
+        // return a fake value to pass getData()
+        HashMap data = new HashMap<String, String>();
+        data.put("pdu", new byte[1]);
+        when(mSmsTracker.getData()).thenReturn(data);
+        when(mSmsTracker.getAppPackageName()).thenReturn("");
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 1);
+        // Set isIms() false
+        doReturn(false).when(mSmsDispatchersController).isIms();
+        mContextFixture.putBooleanResource(
+                R.bool.config_satellite_allow_check_message_in_not_connected, true);
+        // Set ServiceState to OOS
+        doReturn(new ServiceState()).when(mPhone).getServiceState();
+        // Set isMtSmsPollingMessage to true
+        when(mSmsTracker.isMtSmsPollingMessage(any())).thenReturn(true);
+
+        mGsmSmsDispatcher.sendRawPdu(new SMSDispatcher.SmsTracker[] {mSmsTracker});
+        processAllMessages();
+
+        verify(mSmsTracker, times(0)).onFailed(any(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void testSmsTracker_isMtSmsPollingMessage_returnsTrue() throws Exception {
+        String mtSmsPollingText = "mt_sms_polling_text";
+        mContextFixture.putResource(R.string.config_mt_sms_polling_text, mtSmsPollingText);
+        SMSDispatcher.SmsTracker tracker =
+                new SMSDispatcher.SmsTracker("destAddr", 0L, mtSmsPollingText);
+
+        boolean isMtSmsPollingMessage = tracker.isMtSmsPollingMessage(mContext);
+
+        assertTrue(isMtSmsPollingMessage);
+    }
+
+    @Test
+    public void testSmsTracker_isMtSmsPollingMessage_returnsFalse() throws Exception {
+        mContextFixture.putResource(R.string.config_mt_sms_polling_text, "mt_sms_polling_text");
+        SMSDispatcher.SmsTracker tracker =
+                new SMSDispatcher.SmsTracker("destAddr", 0L, "wrong_text");
+
+        boolean isMtSmsPollingMessage = tracker.isMtSmsPollingMessage(mContext);
+
+        assertFalse(isMtSmsPollingMessage);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
index 4abf33f..130fba8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -46,6 +47,7 @@
 import android.os.Looper;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.TelephonyManager;
@@ -69,8 +71,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -95,13 +99,14 @@
             "TestCarrier2Pkg", "Carrier2ImsService");
 
     private static final int NUM_MAX_SLOTS = 2;
-    private static final String TAG = ImsResolverTest.class.getSimpleName();
+    private static final UserHandle TEST_USER_HANDLE = UserHandle.of(Integer.MAX_VALUE);
 
     // Mocked classes
     Context mMockContext;
     PackageManager mMockPM;
     ImsResolver.SubscriptionManagerProxy mTestSubscriptionManagerProxy;
     ImsResolver.TelephonyManagerProxy mTestTelephonyManagerProxy;
+    ImsResolver.ActivityManagerProxy mTestActivityManagerProxy;
     CarrierConfigManager mMockCarrierConfigManager;
     UserManager mMockUserManager;
     ImsResolver.ImsDynamicQueryManagerFactory mMockQueryManagerFactory;
@@ -112,6 +117,7 @@
     private BroadcastReceiver mTestPackageBroadcastReceiver;
     private BroadcastReceiver mTestCarrierConfigReceiver;
     private BroadcastReceiver mTestBootCompleteReceiver;
+    private BroadcastReceiver mTestUserChangedReceiver;
     private ImsServiceFeatureQueryManager.Listener mDynamicQueryListener;
     private PersistableBundle[] mCarrierConfigs;
     private FeatureFlags mFeatureFlags;
@@ -124,12 +130,14 @@
         mMockPM = mock(PackageManager.class);
         mTestSubscriptionManagerProxy = mock(ImsResolver.SubscriptionManagerProxy.class);
         mTestTelephonyManagerProxy = mock(ImsResolver.TelephonyManagerProxy.class);
+        mTestActivityManagerProxy = mock(ImsResolver.ActivityManagerProxy.class);
         mMockCarrierConfigManager = mock(CarrierConfigManager.class);
         mMockUserManager = mock(UserManager.class);
         mMockQueryManagerFactory = mock(ImsResolver.ImsDynamicQueryManagerFactory.class);
         mMockQueryManager = mock(ImsServiceFeatureQueryManager.class);
         mMockRepo = mock(ImsFeatureBinderRepository.class);
         mFeatureFlags = mock(FeatureFlags.class);
+        when(mFeatureFlags.imsResolverUserAware()).thenReturn(true);
     }
 
     @After
@@ -411,7 +419,7 @@
 
         ArgumentCaptor<SparseIntArray> arrayCaptor =
                         ArgumentCaptor.forClass(SparseIntArray.class);
-        verify(controller).bind(eq(features), arrayCaptor.capture());
+        verify(controller).bind(eq(mContext.getUser()), eq(features), arrayCaptor.capture());
         SparseIntArray slotIdToSubIdMap = arrayCaptor.getValue();
         SparseIntArray compareMap = new SparseIntArray();
         compareMap.put(0, 0);
@@ -469,11 +477,14 @@
         when(mMockQueryManager.isQueryInProgress()).thenReturn(false);
         setupDynamicQueryFeatures(TEST_CARRIER_2_DEFAULT_NAME, featuresAll, 1);
 
-        verify(deviceController).bind(eq(featuresDevice), any(SparseIntArray.class));
+        verify(deviceController).bind(eq(mContext.getUser()), eq(featuresDevice),
+                any(SparseIntArray.class));
         verify(deviceController, never()).unbind();
-        verify(carrierController1).bind(eq(featuresMmTel), any(SparseIntArray.class));
+        verify(carrierController1).bind(eq(mContext.getUser()), eq(featuresMmTel),
+                any(SparseIntArray.class));
         verify(carrierController1, never()).unbind();
-        verify(carrierController2).bind(eq(featuresRcs), any(SparseIntArray.class));
+        verify(carrierController2).bind(eq(mContext.getUser()), eq(featuresRcs),
+                any(SparseIntArray.class));
         verify(carrierController2, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController1.getComponentName());
         assertEquals(TEST_CARRIER_2_DEFAULT_NAME, carrierController2.getComponentName());
@@ -521,11 +532,14 @@
         when(mMockQueryManager.isQueryInProgress()).thenReturn(false);
         setupDynamicQueryFeatures(TEST_CARRIER_2_DEFAULT_NAME, allFeatures, 1);
 
-        verify(deviceController, never()).bind(any(), any(SparseIntArray.class));
+        verify(deviceController, never()).bind(eq(mContext.getUser()), any(),
+                any(SparseIntArray.class));
         verify(deviceController, never()).unbind();
-        verify(carrierController1).bind(eq(featuresMmTel), any(SparseIntArray.class));
+        verify(carrierController1).bind(eq(mContext.getUser()), eq(featuresMmTel),
+                any(SparseIntArray.class));
         verify(carrierController1, never()).unbind();
-        verify(carrierController2).bind(eq(featuresRcs), any(SparseIntArray.class));
+        verify(carrierController2).bind(eq(mContext.getUser()), eq(featuresRcs),
+                any(SparseIntArray.class));
         verify(carrierController2, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController1.getComponentName());
         assertEquals(TEST_CARRIER_2_DEFAULT_NAME, carrierController2.getComponentName());
@@ -553,7 +567,7 @@
         startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
 
-        verify(controller).bind(eq(features), any(SparseIntArray.class));
+        verify(controller).bind(eq(mContext.getUser()), eq(features), any(SparseIntArray.class));
         verify(controller, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName());
     }
@@ -582,7 +596,7 @@
         // We will not bind with FEATURE_EMERGENCY_MMTEL
         features.remove(new ImsFeatureConfiguration.FeatureSlotPair(0,
                 ImsFeature.FEATURE_EMERGENCY_MMTEL));
-        verify(controller).bind(eq(features), any(SparseIntArray.class));
+        verify(controller).bind(eq(mContext.getUser()), eq(features), any(SparseIntArray.class));
         verify(controller, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName());
     }
@@ -606,20 +620,23 @@
         setupPackageQuery(info);
         ImsServiceController deviceController1 = mock(ImsServiceController.class);
         ImsServiceController deviceController2 = mock(ImsServiceController.class);
-        setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
+        setImsServiceControllerDDCFactory(deviceController1, deviceController2, null);
         // Bind using default features
         startBindNoCarrierConfig(1);
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet1 =
                 convertToHashSet(featuresController1, 0);
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet2 =
                 convertToHashSet(featuresController2, 0);
-        verify(deviceController1).bind(eq(featureSet1), any(SparseIntArray.class));
-        verify(deviceController2).bind(eq(featureSet2), any(SparseIntArray.class));
+        verify(deviceController1).bind(eq(mContext.getUser()), eq(featureSet1),
+                any(SparseIntArray.class));
+        verify(deviceController2).bind(eq(mContext.getUser()), eq(featureSet2),
+                any(SparseIntArray.class));
         // simulate ImsServiceController binding and setup
-        mTestImsResolver.imsServiceFeatureCreated(0, ImsFeature.FEATURE_EMERGENCY_MMTEL,
+        mTestImsResolver.imsServiceFeatureCreated(0, 0, ImsFeature.FEATURE_EMERGENCY_MMTEL,
                 deviceController1);
-        mTestImsResolver.imsServiceFeatureCreated(0, ImsFeature.FEATURE_MMTEL, deviceController1);
-        mTestImsResolver.imsServiceFeatureCreated(0, ImsFeature.FEATURE_RCS, deviceController2);
+        mTestImsResolver.imsServiceFeatureCreated(0, 0, ImsFeature.FEATURE_MMTEL,
+                deviceController1);
+        mTestImsResolver.imsServiceFeatureCreated(0, 0, ImsFeature.FEATURE_RCS, deviceController2);
 
         mTestImsResolver.enableIms(0 /*slotId*/);
         // Verify enableIms is only called once per controller.
@@ -651,7 +668,7 @@
         // Bind without emergency calling
         startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
-        verify(controller).bind(eq(features), any(SparseIntArray.class));
+        verify(controller).bind(eq(mContext.getUser()), eq(features), any(SparseIntArray.class));
         verify(controller, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, controller.getComponentName());
 
@@ -686,8 +703,8 @@
         startBindCarrierConfigAlreadySet();
 
         processAllMessages();
-        verify(mMockQueryManager, never()).startQuery(any(), any());
-        verify(controller, never()).bind(any(), any(SparseIntArray.class));
+        verify(mMockQueryManager, never()).startQuery(any(), any(), any());
+        verify(controller, never()).bind(any(), any(), any(SparseIntArray.class));
         verify(controller, never()).unbind();
     }
 
@@ -719,7 +736,7 @@
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet = convertToHashSet(features, 0);
         ArgumentCaptor<SparseIntArray> arrayCaptor =
                         ArgumentCaptor.forClass(SparseIntArray.class);
-        verify(controller).bind(eq(featureSet), arrayCaptor.capture());
+        verify(controller).bind(eq(mContext.getUser()), eq(featureSet), arrayCaptor.capture());
         SparseIntArray slotIdToSubIdMap = arrayCaptor.getValue();
         SparseIntArray compareMap = new SparseIntArray();
         compareMap.put(0, 0);
@@ -729,7 +746,7 @@
         }
 
         verify(controller, never()).unbind();
-        verify(mMockQueryManager, never()).startQuery(any(), any());
+        verify(mMockQueryManager, never()).startQuery(any(), any(), any());
         assertEquals(TEST_DEVICE_DEFAULT_NAME, controller.getComponentName());
     }
 
@@ -763,7 +780,7 @@
         featureSet.addAll(convertToHashSet(features, 1));
         ArgumentCaptor<SparseIntArray> arrayCaptor =
                         ArgumentCaptor.forClass(SparseIntArray.class);
-        verify(controller).bind(eq(featureSet), arrayCaptor.capture());
+        verify(controller).bind(eq(mContext.getUser()), eq(featureSet), arrayCaptor.capture());
         SparseIntArray slotIdToSubIdMap = arrayCaptor.getValue();
         assertEquals(slotIdToSubIdMap.size(), 2);
         SparseIntArray compareMap = new SparseIntArray();
@@ -774,7 +791,7 @@
             assertEquals(slotIdToSubIdMap.get(i), compareMap.get(i));
         }
         verify(controller, never()).unbind();
-        verify(mMockQueryManager, never()).startQuery(any(), any());
+        verify(mMockQueryManager, never()).startQuery(any(), any(), any());
         assertEquals(TEST_DEVICE_DEFAULT_NAME, controller.getComponentName());
 
         // Change number of SIMs and verify the features in the ImsServiceController are changed
@@ -823,7 +840,7 @@
 
         ArgumentCaptor<SparseIntArray> arrayCaptor =
                         ArgumentCaptor.forClass(SparseIntArray.class);
-        verify(controller).bind(eq(featureSet), arrayCaptor.capture());
+        verify(controller).bind(eq(mContext.getUser()), eq(featureSet), arrayCaptor.capture());
         SparseIntArray slotIdToSubIdMap = arrayCaptor.getValue();
         assertEquals(slotIdToSubIdMap.size(), 1);
         SparseIntArray compareMap = new SparseIntArray();
@@ -833,7 +850,7 @@
             assertEquals(slotIdToSubIdMap.get(i), compareMap.get(i));
         }
         verify(controller, never()).unbind();
-        verify(mMockQueryManager, never()).startQuery(any(), any());
+        verify(mMockQueryManager, never()).startQuery(any(), any(), any());
         assertEquals(TEST_DEVICE_DEFAULT_NAME, controller.getComponentName());
 
         // Change number of SIMs and verify the features in the ImsServiceController are changed
@@ -895,17 +912,19 @@
         processAllMessages();
         // ensure that startQuery was called
         verify(mMockQueryManager, times(1)).startQuery(eq(TEST_DEVICE_DEFAULT_NAME),
-                any(String.class));
+                any(UserHandle.class), any(String.class));
 
         verify(mMockQueryManager, times(1)).startQuery(eq(TEST_DEVICE2_DEFAULT_NAME),
-                any(String.class));
+                any(UserHandle.class), any(String.class));
 
         mDynamicQueryListener.onComplete(TEST_DEVICE_DEFAULT_NAME, deviceFeatures1);
         mDynamicQueryListener.onComplete(TEST_DEVICE2_DEFAULT_NAME, deviceFeatures2);
         processAllMessages();
 
-        verify(deviceController, times(2)).bind(eq(deviceFeatures1), any(SparseIntArray.class));
-        verify(deviceController2, times(1)).bind(eq(deviceFeatures2), any(SparseIntArray.class));
+        verify(deviceController, times(2)).bind(eq(mContext.getUser()), eq(deviceFeatures1),
+                any(SparseIntArray.class));
+        verify(deviceController2, times(1)).bind(eq(mContext.getUser()), eq(deviceFeatures2),
+                any(SparseIntArray.class));
     }
 
     /**
@@ -942,7 +961,8 @@
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Verify that all features that have been defined for the carrier override are bound
-        verify(carrierController).bind(eq(carrierFeatures), any(SparseIntArray.class));
+        verify(carrierController).bind(eq(mContext.getUser()), eq(carrierFeatures),
+                any(SparseIntArray.class));
         verify(carrierController, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController.getComponentName());
         // Verify that all features that are not defined in the carrier override are bound in the
@@ -950,7 +970,8 @@
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
                 convertToHashSet(deviceFeatures, 0);
         deviceFeatureSet.removeAll(carrierFeatures);
-        verify(deviceController).bind(eq(deviceFeatureSet), any(SparseIntArray.class));
+        verify(deviceController).bind(eq(mContext.getUser()), eq(deviceFeatureSet),
+                any(SparseIntArray.class));
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
 
@@ -1006,7 +1027,8 @@
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Verify that all features that have been defined for the carrier override are bound
-        verify(carrierController).bind(eq(carrierFeatures), any(SparseIntArray.class));
+        verify(carrierController).bind(eq(mContext.getUser()), eq(carrierFeatures),
+                any(SparseIntArray.class));
         verify(carrierController, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController.getComponentName());
         // Verify that all features that are not defined in the carrier override are bound in the
@@ -1015,7 +1037,8 @@
                 convertToHashSet(deviceFeatures, 0);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 1));
         deviceFeatureSet.removeAll(carrierFeatures);
-        verify(deviceController).bind(eq(deviceFeatureSet), any(SparseIntArray.class));
+        verify(deviceController).bind(eq(mContext.getUser()), eq(deviceFeatureSet),
+                any(SparseIntArray.class));
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
 
@@ -1062,9 +1085,9 @@
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet = convertToHashSet(features, 0);
         // There is no carrier override set, so make sure that the ImsServiceController binds
         // to all SIMs.
-        verify(controller).bind(eq(featureSet), any(SparseIntArray.class));
+        verify(controller).bind(eq(mContext.getUser()), eq(featureSet), any(SparseIntArray.class));
         verify(controller, never()).unbind();
-        verify(mMockQueryManager, never()).startQuery(any(), any());
+        verify(mMockQueryManager, never()).startQuery(any(), any(), any());
         assertEquals(TEST_DEVICE_DEFAULT_NAME, controller.getComponentName());
     }
 
@@ -1100,7 +1123,8 @@
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Verify that all features that have been defined for the carrier override are bound
-        verify(carrierController).bind(eq(carrierFeatures), any(SparseIntArray.class));
+        verify(carrierController).bind(eq(mContext.getUser()), eq(carrierFeatures),
+                any(SparseIntArray.class));
         verify(carrierController, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController.getComponentName());
         // Verify that all features that are not defined in the carrier override are bound in the
@@ -1108,7 +1132,8 @@
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
                 convertToHashSet(deviceFeatures, 0);
         deviceFeatureSet.removeAll(carrierFeatures);
-        verify(deviceController).bind(eq(deviceFeatureSet), any(SparseIntArray.class));
+        verify(deviceController).bind(eq(mContext.getUser()), eq(deviceFeatureSet),
+                any(SparseIntArray.class));
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
     }
@@ -1127,11 +1152,11 @@
 
         // Callback from mock ImsServiceControllers
         // All features on slot 1 should be the device default
-        mTestImsResolver.imsServiceFeatureCreated(1, ImsFeature.FEATURE_MMTEL, deviceController);
-        mTestImsResolver.imsServiceFeatureCreated(1, ImsFeature.FEATURE_RCS, deviceController);
-        mTestImsResolver.imsServiceFeatureCreated(0, ImsFeature.FEATURE_MMTEL, deviceController);
+        mTestImsResolver.imsServiceFeatureCreated(1, 1, ImsFeature.FEATURE_MMTEL, deviceController);
+        mTestImsResolver.imsServiceFeatureCreated(1, 1, ImsFeature.FEATURE_RCS, deviceController);
+        mTestImsResolver.imsServiceFeatureCreated(0, 0, ImsFeature.FEATURE_MMTEL, deviceController);
         // The carrier override contains this feature
-        mTestImsResolver.imsServiceFeatureCreated(0, ImsFeature.FEATURE_RCS, carrierController);
+        mTestImsResolver.imsServiceFeatureCreated(0, 0, ImsFeature.FEATURE_RCS, carrierController);
     }
 
     /**
@@ -1155,7 +1180,7 @@
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet =
                 convertToHashSet(features, 0);
         featureSet.addAll(convertToHashSet(features, 1));
-        verify(controller).bind(eq(featureSet), any(SparseIntArray.class));
+        verify(controller).bind(eq(mContext.getUser()), eq(featureSet), any(SparseIntArray.class));
 
         // add RCS to features list
         Set<String> newFeatures = new HashSet<>(features);
@@ -1191,7 +1216,7 @@
         setupPackageQuery(info);
         ImsServiceController deviceController1 = mock(ImsServiceController.class);
         ImsServiceController deviceController2 = mock(ImsServiceController.class);
-        setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
+        setImsServiceControllerDDCFactory(deviceController1, deviceController2, null);
         // Bind using default features
         startBindNoCarrierConfig(2);
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet1 =
@@ -1200,8 +1225,10 @@
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet2 =
                 convertToHashSet(featuresController2, 0);
         featureSet2.addAll(convertToHashSet(featuresController2, 1));
-        verify(deviceController1).bind(eq(featureSet1), any(SparseIntArray.class));
-        verify(deviceController2).bind(eq(featureSet2), any(SparseIntArray.class));
+        verify(deviceController1).bind(eq(mContext.getUser()), eq(featureSet1),
+                any(SparseIntArray.class));
+        verify(deviceController2).bind(eq(mContext.getUser()), eq(featureSet2),
+                any(SparseIntArray.class));
 
         // add RCS to features list for device 1
         Set<String> newFeatures1 = new HashSet<>(featuresController1);
@@ -1239,7 +1266,7 @@
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet =
                 convertToHashSet(features, 0);
         featureSet.addAll(convertToHashSet(features, 1));
-        verify(controller).bind(eq(featureSet), any(SparseIntArray.class));
+        verify(controller).bind(eq(mContext.getUser()), eq(featureSet), any(SparseIntArray.class));
 
         // add RCS to features list
         Set<String> newFeatures = new HashSet<>(features);
@@ -1288,7 +1315,8 @@
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Verify that all features that have been defined for the carrier override are bound
-        verify(carrierController).bind(eq(carrierFeatures), any(SparseIntArray.class));
+        verify(carrierController).bind(eq(mContext.getUser()), eq(carrierFeatures),
+                any(SparseIntArray.class));
         verify(carrierController, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController.getComponentName());
         // Verify that all features that are not defined in the carrier override are bound in the
@@ -1297,7 +1325,8 @@
                 convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
         deviceFeatureSet.removeAll(carrierFeatures);
-        verify(deviceController).bind(eq(deviceFeatureSet), any(SparseIntArray.class));
+        verify(deviceController).bind(eq(mContext.getUser()), eq(deviceFeatureSet),
+                any(SparseIntArray.class));
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
 
@@ -1356,13 +1385,13 @@
         ImsServiceController deviceController1 = mock(ImsServiceController.class);
         ImsServiceController deviceController2 = mock(ImsServiceController.class);
         ImsServiceController carrierController = mock(ImsServiceController.class);
-        setImsServiceControllerFactory(deviceController1, deviceController2, carrierController,
-                null);
+        setImsServiceControllerDDCFactory(deviceController1, deviceController2, carrierController);
 
         startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
         // Verify that all features that have been defined for the carrier override are bound
-        verify(carrierController).bind(eq(carrierFeatures), any(SparseIntArray.class));
+        verify(carrierController).bind(eq(mContext.getUser()), eq(carrierFeatures),
+                any(SparseIntArray.class));
         verify(carrierController, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController.getComponentName());
         // Verify that all features that are not defined in the carrier override are bound in the
@@ -1370,13 +1399,16 @@
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet1 =
                 convertToHashSet(deviceFeatures1, 1);
         deviceFeatureSet1.removeAll(carrierFeatures);
-        verify(deviceController1).bind(eq(deviceFeatureSet1), any(SparseIntArray.class));
+        verify(deviceController1).bind(eq(mContext.getUser()), eq(deviceFeatureSet1),
+                any(SparseIntArray.class));
         verify(deviceController1, never()).unbind();
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet2 =
                 convertToHashSet(deviceFeatures2, 0);
         deviceFeatureSet2.addAll(convertToHashSet(deviceFeatures2, 1));
+        deviceFeatureSet2.addAll(convertToHashSet(deviceFeatures2, 1));
         deviceFeatureSet2.removeAll(carrierFeatures);
-        verify(deviceController2).bind(eq(deviceFeatureSet2), any(SparseIntArray.class));
+        verify(deviceController2).bind(eq(mContext.getUser()), eq(deviceFeatureSet2),
+                any(SparseIntArray.class));
         verify(deviceController2, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController1.getComponentName());
         assertEquals(TEST_DEVICE2_DEFAULT_NAME, deviceController2.getComponentName());
@@ -1392,7 +1424,7 @@
         verify(carrierController).changeImsServiceFeatures(eq(carrierFeatures),
                 any(SparseIntArray.class));
         deviceFeatureSet1.removeAll(carrierFeatures);
-        verify(deviceController1, times(2)).changeImsServiceFeatures(eq(deviceFeatureSet1),
+        verify(deviceController1).changeImsServiceFeatures(eq(deviceFeatureSet1),
                 any(SparseIntArray.class));
         deviceFeatureSet2.removeAll(carrierFeatures);
         verify(deviceController2).changeImsServiceFeatures(eq(deviceFeatureSet2),
@@ -1429,7 +1461,8 @@
         startBindCarrierConfigAlreadySet();
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
         // Verify that all features that have been defined for the carrier override are bound
-        verify(carrierController).bind(eq(carrierFeatures), any(SparseIntArray.class));
+        verify(carrierController).bind(eq(mContext.getUser()), eq(carrierFeatures),
+                any(SparseIntArray.class));
         verify(carrierController, never()).unbind();
         assertEquals(TEST_CARRIER_DEFAULT_NAME, carrierController.getComponentName());
         // Verify that all features that are not defined in the carrier override are bound in the
@@ -1438,7 +1471,8 @@
                 convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
         deviceFeatureSet.removeAll(carrierFeatures);
-        verify(deviceController).bind(eq(deviceFeatureSet), any(SparseIntArray.class));
+        verify(deviceController).bind(eq(mContext.getUser()), eq(deviceFeatureSet),
+                any(SparseIntArray.class));
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
 
@@ -1497,14 +1531,20 @@
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Verify that all features that have been defined for the carrier override are bound
-        verify(carrierController).bind(eq(carrierFeatures), any(SparseIntArray.class));
+        verify(carrierController).bind(eq(mContext.getUser()), eq(carrierFeatures),
+                any(SparseIntArray.class));
         // device features change
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
                 convertToHashSet(deviceFeatures, 1);
         deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
         deviceFeatureSet.removeAll(carrierFeatures);
-        verify(deviceController).changeImsServiceFeatures(eq(deviceFeatureSet),
-                any(SparseIntArray.class));
+        if (mFeatureFlags.imsResolverUserAware()) {
+            verify(deviceController).changeImsServiceFeatures(eq(deviceFeatureSet),
+                    any(SparseIntArray.class));
+        } else {
+            verify(deviceController).bind(eq(mContext.getUser()), eq(deviceFeatureSet),
+                    any(SparseIntArray.class));
+        }
     }
 
     /**
@@ -1644,7 +1684,8 @@
         assertNotNull(mTestImsResolver.getImsServiceInfoFromCache(
                 TEST_CARRIER_DEFAULT_NAME.getPackageName()));
         // Verify that carrier 2 is bound
-        verify(carrierController2).bind(eq(carrierFeatures2), any(SparseIntArray.class));
+        verify(carrierController2).bind(eq(mContext.getUser()), eq(carrierFeatures2),
+                any(SparseIntArray.class));
         assertNotNull(mTestImsResolver.getImsServiceInfoFromCache(
                 TEST_CARRIER_2_DEFAULT_NAME.getPackageName()));
         // device features change to accommodate for the features carrier 2 lacks
@@ -1692,7 +1733,8 @@
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Verify that all features that have been defined for the carrier override are bound
-        verify(carrierController).bind(eq(carrierFeatures), any(SparseIntArray.class));
+        verify(carrierController).bind(eq(mContext.getUser()), eq(carrierFeatures),
+                any(SparseIntArray.class));
         // device features change
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
                 convertToHashSet(deviceFeatures, 1);
@@ -1733,13 +1775,15 @@
         setupDynamicQueryFeaturesFailure(TEST_CARRIER_DEFAULT_NAME, 1);
 
         // Verify that a bind never occurs for the carrier controller.
-        verify(carrierController, never()).bind(any(), any(SparseIntArray.class));
+        verify(carrierController, never()).bind(eq(mContext.getUser()), any(),
+                any(SparseIntArray.class));
         verify(carrierController, never()).unbind();
         // Verify that all features are used to bind to the device ImsService since the carrier
         // ImsService failed to bind properly.
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
                 convertToHashSet(deviceFeatures, 0);
-        verify(deviceController).bind(eq(deviceFeatureSet), any(SparseIntArray.class));
+        verify(deviceController).bind(eq(mContext.getUser()), eq(deviceFeatureSet),
+                any(SparseIntArray.class));
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
     }
@@ -1774,18 +1818,21 @@
         setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
 
         // Verify that a bind never occurs for the carrier controller.
-        verify(carrierController).bind(eq(carrierFeatures), any(SparseIntArray.class));
+        verify(carrierController).bind(eq(mContext.getUser()), eq(carrierFeatures),
+                any(SparseIntArray.class));
         verify(carrierController, never()).unbind();
         // Verify that all features that are not defined in the carrier override are bound in the
         // device controller (including emergency voice for slot 0)
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
                 convertToHashSet(deviceFeatures, 0);
         deviceFeatureSet.removeAll(carrierFeatures);
-        verify(deviceController).bind(eq(deviceFeatureSet), any(SparseIntArray.class));
+        verify(deviceController).bind(eq(mContext.getUser()), eq(deviceFeatureSet),
+                any(SparseIntArray.class));
         verify(deviceController, never()).unbind();
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
 
-        mTestImsResolver.imsServiceBindPermanentError(TEST_CARRIER_DEFAULT_NAME);
+        mTestImsResolver.imsServiceBindPermanentError(TEST_CARRIER_DEFAULT_NAME,
+                mContext.getUser());
         processAllMessages();
         verify(carrierController).unbind();
         // Verify that the device ImsService features are changed to include the ones previously
@@ -1815,7 +1862,7 @@
         setupPackageQuery(info);
         ImsServiceController deviceController1 = mock(ImsServiceController.class);
         ImsServiceController deviceController2 = mock(ImsServiceController.class);
-        setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
+        setImsServiceControllerDDCFactory(deviceController1, deviceController2, null);
 
         startBindNoCarrierConfig(1);
         processAllMessages();
@@ -1825,11 +1872,12 @@
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureResultSet =
                 convertToHashSet(featureResult, 0);
         featureResultSet.addAll(convertToHashSet(featureResult, 1));
-        verify(deviceController1).bind(eq(featureResultSet), any(SparseIntArray.class));
+        verify(deviceController1).bind(eq(mContext.getUser()), eq(featureResultSet),
+                any(SparseIntArray.class));
         verify(deviceController1, never()).unbind();
-        verify(deviceController2, never()).bind(any(), any(SparseIntArray.class));
+        verify(deviceController2, never()).bind(any(), any(), any(SparseIntArray.class));
         verify(deviceController2, never()).unbind();
-        verify(mMockQueryManager, never()).startQuery(any(), any());
+        verify(mMockQueryManager, never()).startQuery(any(), any(), any());
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController1.getComponentName());
     }
 
@@ -1852,7 +1900,7 @@
         setupPackageQuery(info);
         ImsServiceController deviceController1 = mock(ImsServiceController.class);
         ImsServiceController deviceController2 = mock(ImsServiceController.class);
-        setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
+        setImsServiceControllerDDCFactory(deviceController1, deviceController2, null);
 
         startBindNoCarrierConfig(1);
         processAllMessages();
@@ -1862,11 +1910,12 @@
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureResultSet =
                 convertToHashSet(featureResult, 0);
         featureResultSet.addAll(convertToHashSet(featureResult, 1));
-        verify(deviceController1).bind(eq(featureResultSet), any(SparseIntArray.class));
+        verify(deviceController1).bind(eq(mContext.getUser()), eq(featureResultSet),
+                any(SparseIntArray.class));
         verify(deviceController1, never()).unbind();
-        verify(deviceController2, never()).bind(any(), any(SparseIntArray.class));
+        verify(deviceController2, never()).bind(any(), any(), any(SparseIntArray.class));
         verify(deviceController2, never()).unbind();
-        verify(mMockQueryManager, never()).startQuery(any(), any());
+        verify(mMockQueryManager, never()).startQuery(any(), any(), any());
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController1.getComponentName());
     }
 
@@ -1893,7 +1942,7 @@
         setupPackageQuery(info);
         ImsServiceController deviceController1 = mock(ImsServiceController.class);
         ImsServiceController deviceController2 = mock(ImsServiceController.class);
-        setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
+        setImsServiceControllerDDCFactory(deviceController1, deviceController2, null);
 
         startBindNoCarrierConfig(1);
         processAllMessages();
@@ -1904,11 +1953,13 @@
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet2 =
                 convertToHashSet(features2, 0);
         featureSet2.addAll(convertToHashSet(features2, 1));
-        verify(deviceController1).bind(eq(featureSet1), any(SparseIntArray.class));
+        verify(deviceController1).bind(eq(mContext.getUser()), eq(featureSet1),
+                any(SparseIntArray.class));
         verify(deviceController1, never()).unbind();
-        verify(deviceController2).bind(eq(featureSet2), any(SparseIntArray.class));
+        verify(deviceController2).bind(eq(mContext.getUser()), eq(featureSet2),
+                any(SparseIntArray.class));
         verify(deviceController2, never()).unbind();
-        verify(mMockQueryManager, never()).startQuery(any(), any());
+        verify(mMockQueryManager, never()).startQuery(any(), any(), any());
         assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController1.getComponentName());
         assertEquals(TEST_DEVICE2_DEFAULT_NAME, deviceController2.getComponentName());
     }
@@ -1934,16 +1985,134 @@
         setupPackageQuery(info);
         ImsServiceController deviceController1 = mock(ImsServiceController.class);
         ImsServiceController deviceController2 = mock(ImsServiceController.class);
-        setImsServiceControllerFactory(deviceController1, deviceController2, null, null);
+        setImsServiceControllerDDCFactory(deviceController1, deviceController2, null);
 
         startBindNoCarrierConfig(1);
         processAllMessages();
 
-        verify(deviceController1, never()).bind(any(), any(SparseIntArray.class));
+        verify(deviceController1, never()).bind(any(), any(), any(SparseIntArray.class));
         verify(deviceController1, never()).unbind();
-        verify(deviceController2, never()).bind(any(), any(SparseIntArray.class));
+        verify(deviceController2, never()).bind(any(), any(), any(SparseIntArray.class));
         verify(deviceController2, never()).unbind();
-        verify(mMockQueryManager, never()).startQuery(any(), any());
+        verify(mMockQueryManager, never()).startQuery(any(), any(), any());
+    }
+
+    /**
+     * Change the current active user while having ImsServices in system user. The ImsService config
+     * should not change.
+     */
+    @Test
+    @SmallTest
+    public void testChangeCurrentUserServicesInSystem() throws RemoteException {
+        if (!mFeatureFlags.imsResolverUserAware()) {
+            return;
+        }
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        List<ResolveInfo> info = new ArrayList<>();
+        Set<String> deviceFeatures = new HashSet<>();
+        deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Set the carrier override package for slot 0
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
+        // Carrier service doesn't support the voice feature.
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
+        info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+        // Use device default package, which will load the ImsService that the device provides
+        setupPackageQuery(info);
+
+        ImsServiceController deviceController = mock(ImsServiceController.class);
+        ImsServiceController carrierController = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController, carrierController);
+
+        startBindCarrierConfigAlreadySet();
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
+
+        // Perform a user switch
+        userChanged(TEST_USER_HANDLE);
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 2);
+
+
+        // Verify that all features that have been defined for the device/carrier override are bound
+        // and are not changed when the user changes.
+        verify(carrierController).bind(eq(mContext.getUser()), eq(carrierFeatures),
+                any(SparseIntArray.class));
+        verify(carrierController, atLeastOnce()).changeImsServiceFeatures(eq(carrierFeatures),
+                any(SparseIntArray.class));
+        verify(carrierController, never()).unbind();
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 0);
+        deviceFeatureSet.removeAll(carrierFeatures);
+        verify(deviceController).bind(eq(mContext.getUser()), eq(deviceFeatureSet),
+                any(SparseIntArray.class));
+        verify(deviceController, atLeastOnce()).changeImsServiceFeatures(eq(deviceFeatureSet),
+                any(SparseIntArray.class));
+        verify(deviceController, never()).unbind();
+    }
+
+    /**
+     * Change the current active user while having a carrier ImsService installed for second user.
+     * The features should change when the current user changes to the second user and back.
+     */
+    @Test
+    @SmallTest
+    public void testChangeCurrentUserCarrierInSecondUser() throws RemoteException {
+        if (!mFeatureFlags.imsResolverUserAware()) {
+            return;
+        }
+        setupResolver(1 /*numSlots*/, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
+                TEST_DEVICE_DEFAULT_NAME.getPackageName());
+        Set<String> deviceFeatures = new HashSet<>();
+        deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
+        deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+        // Set the carrier override package for slot 0
+        setConfigCarrierStringMmTelRcs(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
+        // Carrier service doesn't support the voice feature.
+        carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+        // Use device default package, which will load the ImsService that the device provides
+        setupPackageQuery(Collections.singletonList(
+                getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true)));
+        setupPackageQueryForUser(Collections.singletonList(
+                        getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true)),
+                TEST_USER_HANDLE);
+
+        ImsServiceController deviceController = mock(ImsServiceController.class);
+        ImsServiceController carrierController = mock(ImsServiceController.class);
+        setImsServiceControllerFactory(deviceController, carrierController);
+
+        startBindCarrierConfigAlreadySet();
+
+        verify(carrierController, never()).bind(eq(mContext.getUser()), any(),
+                any(SparseIntArray.class));
+        verify(deviceController).bind(eq(mContext.getUser()),
+                eq(convertToHashSet(deviceFeatures, 0)), any(SparseIntArray.class));
+
+        // Perform a user switch
+        setBoundImsServiceControllerUser(carrierController, TEST_USER_HANDLE);
+        userChanged(TEST_USER_HANDLE);
+        setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
+
+
+        // Verify the carrier controller was bound only when the user changed
+        verify(carrierController).bind(eq(TEST_USER_HANDLE), eq(carrierFeatures),
+                any(SparseIntArray.class));
+        verify(carrierController, never()).changeImsServiceFeatures(eq(carrierFeatures),
+                any(SparseIntArray.class));
+        verify(carrierController, never()).unbind();
+
+        HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+                convertToHashSet(deviceFeatures, 0);
+        deviceFeatureSet.removeAll(carrierFeatures);
+        verify(deviceController).changeImsServiceFeatures(eq(deviceFeatureSet),
+                any(SparseIntArray.class));
+        verify(deviceController, never()).unbind();
+    }
+
+    private void setCurrentUser(UserHandle handle) {
+        when(mTestActivityManagerProxy.getCurrentUser()).thenReturn(handle);
     }
 
     private void setupResolver(int numSlots, String deviceMmTelPkgName,
@@ -1970,12 +2139,15 @@
             when(mTestTelephonyManagerProxy.getSimState(any(Context.class), eq(i))).thenReturn(
                     TelephonyManager.SIM_STATE_READY);
         }
+        when(mMockContext.getUser()).thenReturn(mContext.getUser());
+        when(mTestActivityManagerProxy.getCurrentUser()).thenReturn(mContext.getUser());
 
         mTestImsResolver = new ImsResolver(mMockContext, deviceMmTelPkgName, deviceRcsPkgName,
                 numSlots, mMockRepo, Looper.myLooper(), mFeatureFlags);
 
         mTestImsResolver.setSubscriptionManagerProxy(mTestSubscriptionManagerProxy);
         mTestImsResolver.setTelephonyManagerProxy(mTestTelephonyManagerProxy);
+        mTestImsResolver.setActivityManagerProxy(mTestActivityManagerProxy);
         when(mMockQueryManagerFactory.create(any(Context.class),
                 any(ImsServiceFeatureQueryManager.Listener.class))).thenReturn(mMockQueryManager);
         mTestImsResolver.setImsDynamicQueryManagerFactory(mMockQueryManagerFactory);
@@ -1983,24 +2155,55 @@
     }
 
     private void setupPackageQuery(List<ResolveInfo> infos) {
-        // Only return info if not using the compat argument
-        when(mMockPM.queryIntentServicesAsUser(
+        doAnswer((Answer<List<ResolveInfo>>) invocation -> {
+            Intent intent = (Intent) invocation.getArguments()[0];
+            String pkg = intent.getPackage();
+            if (pkg == null) {
+                return infos;
+            } else {
+                for (ResolveInfo info : infos) {
+                    if (pkg.equals(info.serviceInfo.packageName)) {
+                        return Collections.singletonList(info);
+                    }
+                }
+            }
+            return Collections.emptyList();
+        }).when(mMockPM).queryIntentServicesAsUser(
+                // Only return info if not using the compat argument
                 argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
-                anyInt(), any())).thenReturn(infos);
+                anyInt(), any());
+    }
+
+    private void setupPackageQueryForUser(List<ResolveInfo> infos, UserHandle user) {
+        doAnswer((Answer<List<ResolveInfo>>) invocation -> {
+            Intent intent = (Intent) invocation.getArguments()[0];
+            String pkg = intent.getPackage();
+            if (pkg == null) {
+                return infos;
+            } else {
+                for (ResolveInfo info : infos) {
+                    if (pkg.equals(info.serviceInfo.packageName)) {
+                        return Collections.singletonList(info);
+                    }
+                }
+            }
+            return Collections.emptyList();
+        }).when(mMockPM).queryIntentServicesAsUser(
+                // Only return info if not using the compat argument
+                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
+                anyInt(), eq(user));
     }
 
     private void setupPackageQuery(ComponentName name, Set<String> features,
             boolean isPermissionGranted) {
         List<ResolveInfo> info = new ArrayList<>();
         info.add(getResolveInfo(name, features, isPermissionGranted));
-        // Only return info if not using the compat argument
-        when(mMockPM.queryIntentServicesAsUser(
-                argThat(argument -> ImsService.SERVICE_INTERFACE.equals(argument.getAction())),
-                anyInt(), any())).thenReturn(info);
+        setupPackageQuery(info);
     }
 
     private ImsServiceController setupController() {
         ImsServiceController controller = mock(ImsServiceController.class);
+        when(controller.getBoundUser()).thenReturn(mContext.getUser());
         mTestImsResolver.setImsServiceControllerFactory(
                 new ImsResolver.ImsServiceControllerFactory() {
                     @Override
@@ -2028,15 +2231,25 @@
         processAllMessages();
         ArgumentCaptor<BroadcastReceiver> receiversCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mMockContext, times(3)).registerReceiver(receiversCaptor.capture(), any());
-        mTestPackageBroadcastReceiver = receiversCaptor.getAllValues().get(0);
-        mTestCarrierConfigReceiver = receiversCaptor.getAllValues().get(1);
-        mTestBootCompleteReceiver = receiversCaptor.getAllValues().get(2);
+        if (!mFeatureFlags.imsResolverUserAware()) {
+            verify(mMockContext, times(3)).registerReceiver(receiversCaptor.capture(), any());
+            mTestPackageBroadcastReceiver = receiversCaptor.getAllValues().get(0);
+            mTestCarrierConfigReceiver = receiversCaptor.getAllValues().get(1);
+            mTestBootCompleteReceiver = receiversCaptor.getAllValues().get(2);
+        } else {
+            verify(mMockContext, times(4)).registerReceiver(receiversCaptor.capture(), any());
+            mTestPackageBroadcastReceiver = receiversCaptor.getAllValues().get(0);
+            mTestUserChangedReceiver = receiversCaptor.getAllValues().get(1);
+            mTestCarrierConfigReceiver = receiversCaptor.getAllValues().get(2);
+            mTestBootCompleteReceiver = receiversCaptor.getAllValues().get(3);
+
+        }
         ArgumentCaptor<ImsServiceFeatureQueryManager.Listener> queryManagerCaptor =
                 ArgumentCaptor.forClass(ImsServiceFeatureQueryManager.Listener.class);
         verify(mMockQueryManagerFactory).create(any(Context.class), queryManagerCaptor.capture());
         mDynamicQueryListener = queryManagerCaptor.getValue();
-        when(mMockQueryManager.startQuery(any(ComponentName.class), any(String.class)))
+        when(mMockQueryManager.startQuery(any(ComponentName.class), any(UserHandle.class),
+                any(String.class)))
                 .thenReturn(true);
         processAllMessages();
     }
@@ -2050,10 +2263,18 @@
         processAllMessages();
         ArgumentCaptor<BroadcastReceiver> receiversCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mMockContext, times(3)).registerReceiver(receiversCaptor.capture(), any());
-        mTestPackageBroadcastReceiver = receiversCaptor.getAllValues().get(0);
-        mTestCarrierConfigReceiver = receiversCaptor.getAllValues().get(1);
-        mTestBootCompleteReceiver = receiversCaptor.getAllValues().get(2);
+        if (!mFeatureFlags.imsResolverUserAware()) {
+            verify(mMockContext, times(3)).registerReceiver(receiversCaptor.capture(), any());
+            mTestPackageBroadcastReceiver = receiversCaptor.getAllValues().get(0);
+            mTestCarrierConfigReceiver = receiversCaptor.getAllValues().get(1);
+            mTestBootCompleteReceiver = receiversCaptor.getAllValues().get(2);
+        } else {
+            verify(mMockContext, times(4)).registerReceiver(receiversCaptor.capture(), any());
+            mTestPackageBroadcastReceiver = receiversCaptor.getAllValues().get(0);
+            mTestUserChangedReceiver = receiversCaptor.getAllValues().get(1);
+            mTestCarrierConfigReceiver = receiversCaptor.getAllValues().get(2);
+            mTestBootCompleteReceiver = receiversCaptor.getAllValues().get(3);
+        }
         ArgumentCaptor<ImsServiceFeatureQueryManager.Listener> queryManagerCaptor =
                 ArgumentCaptor.forClass(ImsServiceFeatureQueryManager.Listener.class);
         verify(mMockQueryManagerFactory).create(any(Context.class), queryManagerCaptor.capture());
@@ -2069,7 +2290,8 @@
             HashSet<ImsFeatureConfiguration.FeatureSlotPair> features, int times) {
         processAllMessages();
         // ensure that startQuery was called
-        verify(mMockQueryManager, times(times)).startQuery(eq(name), any(String.class));
+        verify(mMockQueryManager, times(times)).startQuery(eq(name), any(UserHandle.class),
+                any(String.class));
         mDynamicQueryListener.onComplete(name, features);
         processAllMessages();
     }
@@ -2078,7 +2300,8 @@
             HashSet<ImsFeatureConfiguration.FeatureSlotPair> features, int times) {
         processAllFutureMessages();
         // ensure that startQuery was called
-        verify(mMockQueryManager, times(times)).startQuery(eq(name), any(String.class));
+        verify(mMockQueryManager, times(times)).startQuery(eq(name), any(UserHandle.class),
+                any(String.class));
         mDynamicQueryListener.onComplete(name, features);
         processAllMessages();
     }
@@ -2086,8 +2309,19 @@
     private void setupDynamicQueryFeaturesFailure(ComponentName name, int times) {
         processAllMessages();
         // ensure that startQuery was called
-        verify(mMockQueryManager, times(times)).startQuery(eq(name), any(String.class));
-        mDynamicQueryListener.onPermanentError(name);
+        verify(mMockQueryManager, times(times)).startQuery(eq(name), any(UserHandle.class),
+                any(String.class));
+        mDynamicQueryListener.onPermanentError(name, mContext.getUser());
+        processAllMessages();
+    }
+
+    public void userChanged(UserHandle newUser) {
+        setCurrentUser(newUser);
+        // Tell the package manager that a new device feature is installed
+        Intent userSwitchedIntent = new Intent();
+        userSwitchedIntent.setAction(Intent.ACTION_USER_SWITCHED);
+        userSwitchedIntent.putExtra(Intent.EXTRA_USER, newUser);
+        mTestUserChangedReceiver.onReceive(null, userSwitchedIntent);
         processAllMessages();
     }
 
@@ -2110,7 +2344,14 @@
         processAllMessages();
     }
 
+    private void setBoundImsServiceControllerUser(ImsServiceController controller,
+            UserHandle handle) {
+        when(controller.getBoundUser()).thenReturn(handle);
+    }
+
     private void setImsServiceControllerFactory(Map<String, ImsServiceController> controllerMap) {
+        controllerMap.values()
+                .forEach(c -> setBoundImsServiceControllerUser(c, mContext.getUser()));
         mTestImsResolver.setImsServiceControllerFactory(
                 new ImsResolver.ImsServiceControllerFactory() {
                     @Override
@@ -2129,6 +2370,8 @@
 
     private void setImsServiceControllerFactory(ImsServiceController deviceController,
             ImsServiceController carrierController) {
+        setBoundImsServiceControllerUser(deviceController, mContext.getUser());
+        setBoundImsServiceControllerUser(carrierController, mContext.getUser());
         mTestImsResolver.setImsServiceControllerFactory(
                 new ImsResolver.ImsServiceControllerFactory() {
                     @Override
@@ -2156,6 +2399,9 @@
 
     private void setImsServiceControllerFactory(ImsServiceController deviceController,
             ImsServiceController carrierController1, ImsServiceController carrierController2) {
+        setBoundImsServiceControllerUser(deviceController, mContext.getUser());
+        setBoundImsServiceControllerUser(carrierController1, mContext.getUser());
+        setBoundImsServiceControllerUser(carrierController2, mContext.getUser());
         mTestImsResolver.setImsServiceControllerFactory(
                 new ImsResolver.ImsServiceControllerFactory() {
                     @Override
@@ -2185,9 +2431,13 @@
                 });
     }
 
-    private void setImsServiceControllerFactory(ImsServiceController deviceController1,
-            ImsServiceController deviceController2, ImsServiceController carrierController1,
-            ImsServiceController carrierController2) {
+    private void setImsServiceControllerDDCFactory(ImsServiceController deviceController1,
+            ImsServiceController deviceController2, ImsServiceController carrierController1) {
+        setBoundImsServiceControllerUser(deviceController1, mContext.getUser());
+        setBoundImsServiceControllerUser(deviceController2, mContext.getUser());
+        if (carrierController1 != null) {
+            setBoundImsServiceControllerUser(carrierController1, mContext.getUser());
+        }
         mTestImsResolver.setImsServiceControllerFactory(
                 new ImsResolver.ImsServiceControllerFactory() {
                     @Override
@@ -2211,10 +2461,6 @@
                                 componentName.getPackageName())) {
                             when(carrierController1.getComponentName()).thenReturn(componentName);
                             return carrierController1;
-                        } else if (TEST_CARRIER_2_DEFAULT_NAME.getPackageName().equals(
-                                componentName.getPackageName())) {
-                            when(carrierController2.getComponentName()).thenReturn(componentName);
-                            return carrierController2;
                         }
                         return null;
                     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java
index 2544fc1..aa6bd7f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerCompatTest.java
@@ -108,9 +108,9 @@
         mMmTelCompatAdapterSpy = spy(new MmTelFeatureCompatAdapter(mMockContext, SLOT_0,
                 mMockMmTelInterfaceAdapter));
         mTestImsServiceController = new ImsServiceControllerCompat(mMockContext, mTestComponentName,
-                mMockCallbacks, mHandler, REBIND_RETRY, mRepo,
+                 mMockCallbacks, mHandler, REBIND_RETRY, mRepo,
                 (a, b, c) -> mMmTelCompatAdapterSpy);
-        when(mMockContext.bindService(any(), any(), anyInt())).thenReturn(true);
+        when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
         when(mMockServiceControllerBinder.createMMTelFeature(anyInt()))
                 .thenReturn(mMockRemoteMMTelFeature);
         when(mMockRemoteMMTelFeature.getConfigInterface()).thenReturn(mMockImsConfig);
@@ -146,8 +146,8 @@
         verify(mMockServiceControllerBinder).createMMTelFeature(SLOT_0);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_1),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
         // Remove the feature
         conn.onBindingDied(mTestComponentName);
@@ -191,8 +191,9 @@
             SparseIntArray slotIdToSubIdMap) {
         ArgumentCaptor<ServiceConnection> serviceCaptor =
                 ArgumentCaptor.forClass(ServiceConnection.class);
-        assertTrue(mTestImsServiceController.bind(testFeatures, slotIdToSubIdMap));
-        verify(mMockContext).bindService(any(), serviceCaptor.capture(), anyInt());
+        assertTrue(mTestImsServiceController.bind(mContext.getUser(), testFeatures,
+                slotIdToSubIdMap));
+        verify(mMockContext).bindServiceAsUser(any(), serviceCaptor.capture(), anyInt(), any());
         return serviceCaptor.getValue();
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
index 65b73fb..5f16d9b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
@@ -39,6 +39,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.telephony.SubscriptionManager;
 import android.telephony.ims.ImsService;
 import android.telephony.ims.aidl.IImsConfig;
@@ -135,6 +136,7 @@
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private ImsServiceController mTestImsServiceController;
     private ImsFeatureBinderRepository mRepo;
+    private UserHandle mUser;
 
     @Before
     @Override
@@ -150,11 +152,12 @@
         mMockCallbacks = mock(ImsServiceController.ImsServiceControllerCallbacks.class);
         mFeatureFlags = mock(FeatureFlags.class);
         mMockContext = mock(Context.class);
+        mUser = UserHandle.of(UserHandle.myUserId());
 
         mRepo = new ImsFeatureBinderRepository();
         mTestImsServiceController = new ImsServiceController(mMockContext, mTestComponentName,
                 mMockCallbacks, mHandler, REBIND_RETRY, mRepo, mFeatureFlags);
-        when(mMockContext.bindService(any(), any(), anyInt())).thenReturn(true);
+        when(mMockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
         when(mMockServiceControllerBinder.createMmTelFeature(anyInt(), anyInt()))
                 .thenReturn(mMockMmTelFeature);
         when(mMockServiceControllerBinder.createRcsFeature(anyInt(), anyInt()))
@@ -198,11 +201,12 @@
 
         SparseIntArray slotIdToSubIdMap = new SparseIntArray();
         slotIdToSubIdMap.put(SLOT_0, SUB_2);
-        assertTrue(mTestImsServiceController.bind(testFeatures, slotIdToSubIdMap.clone()));
+        assertTrue(mTestImsServiceController.bind(mUser, testFeatures, slotIdToSubIdMap.clone()));
 
         int expectedFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                 | Context.BIND_IMPORTANT;
-        verify(mMockContext).bindService(intentCaptor.capture(), any(), eq(expectedFlags));
+        verify(mMockContext).bindServiceAsUser(intentCaptor.capture(), any(), eq(expectedFlags),
+                any());
         Intent testIntent = intentCaptor.getValue();
         assertEquals(ImsService.SERVICE_INTERFACE, testIntent.getAction());
         assertEquals(mTestComponentName, testIntent.getComponent());
@@ -222,9 +226,9 @@
         bindAndConnectService(testFeatures, slotIdToSubIdMap.clone());
 
         // already bound, should return false
-        assertFalse(mTestImsServiceController.bind(testFeatures, slotIdToSubIdMap.clone()));
+        assertFalse(mTestImsServiceController.bind(mUser, testFeatures, slotIdToSubIdMap.clone()));
 
-        verify(mMockContext, times(1)).bindService(any(), any(), anyInt());
+        verify(mMockContext, times(1)).bindServiceAsUser(any(), any(), anyInt(), any());
     }
 
     /**
@@ -250,10 +254,10 @@
         verify(mMockServiceControllerBinder).createRcsFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_RCS), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0),  eq(SUB_2),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0),  eq(SUB_2),
+                eq(ImsFeature.FEATURE_RCS), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
         validateRcsFeatureContainerExists(SLOT_0);
     }
@@ -282,10 +286,10 @@
         verify(mMockServiceControllerBinder).createRcsFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_RCS), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_RCS), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
         validateRcsFeatureContainerExists(SLOT_0);
 
@@ -313,9 +317,9 @@
         verify(mMockServiceControllerBinder).createRcsFeature(SLOT_0, SUB_3);
         verify(mMockServiceControllerBinder, times(2)).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_RCS), any());
-        verify(mMockCallbacks, times(2)).imsServiceFeatureCreated(eq(SLOT_0),
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_3),
                 eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
-        verify(mMockCallbacks, times(2)).imsServiceFeatureCreated(eq(SLOT_0),
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_3),
                 eq(ImsFeature.FEATURE_RCS), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
         validateRcsFeatureContainerExists(SLOT_0);
@@ -341,13 +345,13 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_1, SUB_3);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_1),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(SUB_3),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
         validateMmTelFeatureContainerExists(SLOT_1);
 
@@ -373,12 +377,12 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_0, SUB_4);
         verify(mMockServiceControllerBinder, times(2)).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks, times(2)).imsServiceFeatureCreated(eq(SLOT_0),
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_4),
                 eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_1, SUB_5);
         verify(mMockServiceControllerBinder, times(2)).addFeatureStatusCallback(eq(SLOT_1),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks, times(2)).imsServiceFeatureCreated(eq(SLOT_1),
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(SUB_5),
                 eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
         validateMmTelFeatureContainerExists(SLOT_1);
@@ -411,13 +415,14 @@
         verify(mMockServiceControllerBinder).createEmergencyOnlyMmTelFeature(SLOT_0);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0),
+                eq(SubscriptionManager.INVALID_SUBSCRIPTION_ID), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_1, SUB_3);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_1),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(SUB_3),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExistsWithEmergency(SLOT_0);
         validateMmTelFeatureContainerExistsWithEmergency(SLOT_1);
 
@@ -437,8 +442,9 @@
         verify(mMockServiceControllerBinder).createEmergencyOnlyMmTelFeature(SLOT_1);
         verify(mMockServiceControllerBinder, times(2)).addFeatureStatusCallback(eq(SLOT_1),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks, times(2)).imsServiceFeatureCreated(eq(SLOT_1),
-                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1),
+                eq(SubscriptionManager.INVALID_SUBSCRIPTION_ID), eq(ImsFeature.FEATURE_MMTEL),
+                eq(mTestImsServiceController));
         validateMmTelFeatureContainerExistsWithEmergency(SLOT_0);
         validateMmTelFeatureContainerExistsWithEmergency(SLOT_1);
 
@@ -470,10 +476,10 @@
         verify(mMockServiceControllerBinder).createRcsFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_RCS), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0),  eq(SUB_2),
+                eq(ImsFeature.FEATURE_RCS), eq(mTestImsServiceController));
         validateFeatureContainerExistsWithSipDelegate(ImsFeature.FEATURE_MMTEL, SLOT_0);
         validateFeatureContainerExistsWithSipDelegate(ImsFeature.FEATURE_RCS, SLOT_0);
     }
@@ -499,7 +505,8 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
         // verify CAPABILITY_SIP_DELEGATE_CREATION is not set because MMTEL and RCS are not set.
         validateFeatureContainerDoesNotHaveSipDelegate(ImsFeature.FEATURE_MMTEL, SLOT_0);
@@ -525,9 +532,9 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0),
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
                 eq(ImsFeature.FEATURE_EMERGENCY_MMTEL), eq(mTestImsServiceController));
         // Make sure this callback happens, which will notify the framework of emergency calling
         // availability.
@@ -553,9 +560,11 @@
         verify(mMockServiceControllerBinder).createEmergencyOnlyMmTelFeature(SLOT_0);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0),
+                eq(SubscriptionManager.INVALID_SUBSCRIPTION_ID), eq(ImsFeature.FEATURE_MMTEL),
                 eq(mTestImsServiceController));
         verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0),
+                eq(SubscriptionManager.INVALID_SUBSCRIPTION_ID),
                 eq(ImsFeature.FEATURE_EMERGENCY_MMTEL), eq(mTestImsServiceController));
         // Make sure this callback happens, which will notify the framework of emergency calling
         // availability.
@@ -584,17 +593,17 @@
         verify(mMockServiceControllerBinder, never()).createMmTelFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder, never()).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(SLOT_0),
+        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
                 eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
-        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(SLOT_0),
+        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
                 eq(ImsFeature.FEATURE_EMERGENCY_MMTEL), eq(mTestImsServiceController));
         validateMmTelFeatureContainerDoesntExist(SLOT_0);
         // verify RCS feature is created
         verify(mMockServiceControllerBinder).createRcsFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_RCS), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0),  eq(SUB_2),
+                eq(ImsFeature.FEATURE_RCS), eq(mTestImsServiceController));
         validateRcsFeatureContainerExists(SLOT_0);
     }
 
@@ -798,9 +807,9 @@
 
         long delay = mTestImsServiceController.getRebindDelay();
         waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
-        verify(mMockCallbacks, never()).imsServiceFeatureCreated(anyInt(), anyInt(),
+        verify(mMockCallbacks, never()).imsServiceFeatureCreated(anyInt(), anyInt(), anyInt(),
                 eq(mTestImsServiceController));
-        verify(mMockCallbacks).imsServiceBindPermanentError(eq(mTestComponentName));
+        verify(mMockCallbacks).imsServiceBindPermanentError(eq(mTestComponentName), eq(mUser));
         validateMmTelFeatureContainerDoesntExist(SLOT_0);
         validateRcsFeatureContainerDoesntExist(SLOT_0);
     }
@@ -822,8 +831,8 @@
         bindAndConnectService(testFeatures, slotIdToSubIdMap.clone());
 
         verify(mMockServiceControllerBinder).createRcsFeature(SLOT_0, SUB_2);
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0),  eq(SUB_2),
+                eq(ImsFeature.FEATURE_RCS), eq(mTestImsServiceController));
         validateRcsFeatureContainerDoesntExist(SLOT_0);
     }
 
@@ -843,8 +852,8 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0),  eq(SUB_2),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         // Create a new list with an additional item
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeaturesWithAddition = new HashSet<>(
                 testFeatures);
@@ -859,8 +868,8 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_1, SUB_3);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_1),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1),  eq(SUB_3),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
         validateMmTelFeatureContainerExists(SLOT_1);
     }
@@ -880,8 +889,8 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0),  eq(SUB_2),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         // Create a new list with an additional item
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeaturesWithAddition = new HashSet<>(
                 testFeatures);
@@ -895,8 +904,8 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_1, SUB_3);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_1),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1),  eq(SUB_3),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
         validateMmTelFeatureContainerExists(SLOT_1);
     }
@@ -918,8 +927,8 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
         assertEquals(mMockMmTelBinder, cb.container.imsFeature);
         assertTrue((ImsService.CAPABILITY_EMERGENCY_OVER_MMTEL
@@ -934,9 +943,8 @@
         mTestImsServiceController.changeImsServiceFeatures(testFeaturesWithAddition,
                 slotIdToSubIdMap.clone());
 
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0),
-                eq(ImsFeature.FEATURE_EMERGENCY_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_EMERGENCY_MMTEL), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExistsWithEmergency(SLOT_0);
         assertEquals(mMockMmTelBinder, cb.container.imsFeature);
 
@@ -972,8 +980,8 @@
         verify(mMockServiceControllerBinder).createRcsFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_RCS), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_RCS), eq(mTestImsServiceController));
         validateRcsFeatureContainerExists(SLOT_0);
         // Add FEATURE_EMERGENCY_MMTEL and ensure it doesn't cause MMTEL bind
         HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeaturesWithAddition = new HashSet<>(
@@ -988,9 +996,8 @@
         verify(mMockServiceControllerBinder, never()).createMmTelFeature(SLOT_1, SUB_3);
         verify(mMockServiceControllerBinder, never()).addFeatureStatusCallback(eq(SLOT_1),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(SLOT_1),
-                eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(SLOT_1), eq(SUB_3),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         validateMmTelFeatureContainerDoesntExist(SLOT_1);
     }
 
@@ -1010,8 +1017,8 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
 
         // Call change with the same features and make sure it is disregarded
@@ -1027,7 +1034,7 @@
         verify(mMockServiceControllerBinder, never()).removeFeatureStatusCallback(anyInt(),
                 anyInt(), any());
         verify(mMockCallbacks, times(1)).imsServiceFeatureCreated(eq(SLOT_0),
-                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
+                eq(SUB_2), eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         verify(mMockCallbacks, never()).imsServiceFeatureRemoved(anyInt(), anyInt(), any());
         validateMmTelFeatureContainerExists(SLOT_0);
     }
@@ -1050,13 +1057,13 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_1, SUB_3);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_1),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(SUB_3),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
         validateMmTelFeatureContainerExists(SLOT_1);
         // Create a new list with one less item
@@ -1098,13 +1105,13 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_1, SUB_3);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_1),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(SUB_3),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
         validateMmTelFeatureContainerExists(SLOT_1);
         // Create a new list with one less item
@@ -1149,23 +1156,23 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         verify(mMockServiceControllerBinder).createRcsFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_RCS), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_RCS),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_RCS), eq(mTestImsServiceController));
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_1, SUB_3);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_1),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(SUB_3),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         verify(mMockServiceControllerBinder).createRcsFeature(SLOT_1, SUB_3);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_1),
                 eq(ImsFeature.FEATURE_RCS), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_RCS),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(SUB_3),
+                eq(ImsFeature.FEATURE_RCS), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
         validateMmTelFeatureContainerExists(SLOT_1);
         validateRcsFeatureContainerExists(SLOT_0);
@@ -1205,12 +1212,12 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_0, SUB_4);
         verify(mMockServiceControllerBinder, times(2)).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks, times(2)).imsServiceFeatureCreated(eq(SLOT_0),
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_4),
                 eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         verify(mMockServiceControllerBinder).createRcsFeature(SLOT_0, SUB_4);
         verify(mMockServiceControllerBinder, times(2)).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_RCS), any());
-        verify(mMockCallbacks, times(2)).imsServiceFeatureCreated(eq(SLOT_0),
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_4),
                 eq(ImsFeature.FEATURE_RCS), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
         validateRcsFeatureContainerExists(SLOT_0);
@@ -1235,13 +1242,13 @@
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         verify(mMockServiceControllerBinder).createMmTelFeature(SLOT_1, SUB_3);
         verify(mMockServiceControllerBinder).addFeatureStatusCallback(eq(SLOT_1),
                 eq(ImsFeature.FEATURE_MMTEL), any());
-        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(ImsFeature.FEATURE_MMTEL),
-                eq(mTestImsServiceController));
+        verify(mMockCallbacks).imsServiceFeatureCreated(eq(SLOT_1), eq(SUB_3),
+                eq(ImsFeature.FEATURE_MMTEL), eq(mTestImsServiceController));
         validateMmTelFeatureContainerExists(SLOT_0);
         validateMmTelFeatureContainerExists(SLOT_1);
 
@@ -1291,7 +1298,7 @@
         verify(mMockServiceControllerBinder, never()).createRcsFeature(SLOT_0, SUB_2);
         verify(mMockServiceControllerBinder, never()).removeFeatureStatusCallback(eq(SLOT_0),
                 eq(ImsFeature.FEATURE_RCS), any());
-        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(SLOT_0),
+        verify(mMockCallbacks, never()).imsServiceFeatureCreated(eq(SLOT_0), eq(SUB_2),
                 eq(ImsFeature.FEATURE_RCS), eq(mTestImsServiceController));
         validateMmTelFeatureContainerDoesntExist(SLOT_0);
         validateRcsFeatureContainerDoesntExist(SLOT_0);
@@ -1318,7 +1325,7 @@
         long delay = REBIND_RETRY.getStartDelay();
         waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
         // The service should autobind after rebind event occurs
-        verify(mMockContext, times(2)).bindService(any(), any(), anyInt());
+        verify(mMockContext, times(2)).bindServiceAsUser(any(), any(), anyInt(), any());
     }
 
     /**
@@ -1344,7 +1351,7 @@
         long delay = REBIND_RETRY.getStartDelay();
         waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
         // The service should autobind after rebind event occurs
-        verify(mMockContext, times(2)).bindService(any(), any(), anyInt());
+        verify(mMockContext, times(2)).bindServiceAsUser(any(), any(), anyInt(), any());
     }
 
     /**
@@ -1365,7 +1372,7 @@
         conn.onBindingDied(null /*null*/);
 
         // Be sure that there are no binds before the RETRY_TIMEOUT expires
-        verify(mMockContext, times(1)).bindService(any(), any(), anyInt());
+        verify(mMockContext, times(1)).bindServiceAsUser(any(), any(), anyInt(), any());
     }
 
     /**
@@ -1390,7 +1397,7 @@
         waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
 
         // Unbind should stop the autobind from occurring.
-        verify(mMockContext, times(1)).bindService(any(), any(), anyInt());
+        verify(mMockContext, times(1)).bindServiceAsUser(any(), any(), anyInt(), any());
     }
 
     /**
@@ -1412,10 +1419,10 @@
 
         long delay = REBIND_RETRY.getStartDelay();
         waitForHandlerActionDelayed(mHandler, delay, 2 * delay);
-        mTestImsServiceController.bind(testFeatures, slotIdToSubIdMap.clone());
+        mTestImsServiceController.bind(mUser, testFeatures, slotIdToSubIdMap.clone());
 
         // Should only see two binds, not three from the auto rebind that occurs.
-        verify(mMockContext, times(2)).bindService(any(), any(), anyInt());
+        verify(mMockContext, times(2)).bindServiceAsUser(any(), any(), anyInt(), any());
     }
 
     private void validateMmTelFeatureContainerExists(int slotId) {
@@ -1504,8 +1511,8 @@
             SparseIntArray slotIdToSubIdMap) {
         ArgumentCaptor<ServiceConnection> serviceCaptor =
                 ArgumentCaptor.forClass(ServiceConnection.class);
-        assertTrue(mTestImsServiceController.bind(testFeatures, slotIdToSubIdMap));
-        verify(mMockContext).bindService(any(), serviceCaptor.capture(), anyInt());
+        assertTrue(mTestImsServiceController.bind(mUser, testFeatures, slotIdToSubIdMap));
+        verify(mMockContext).bindServiceAsUser(any(), serviceCaptor.capture(), anyInt(), any());
         return serviceCaptor.getValue();
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
index e371c59..1c0febf 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
@@ -1148,6 +1148,11 @@
         mSatelliteController1.countOfDatagramTypeKeepAliveFail = 2;
         mSatelliteController1.isProvisioned = true;
         mSatelliteController1.carrierId = 1;
+        mSatelliteController1.countOfSuccessfulLocationQueries = 3;
+        mSatelliteController1.countOfFailedLocationQueries = 5;
+        mSatelliteController1.countOfP2PSmsAvailableNotificationShown = 3;
+        mSatelliteController1.countOfP2PSmsAvailableNotificationRemoved = 5;
+        mSatelliteController1.isNtnOnlyCarrier = false;
 
         mSatelliteController2 = new SatelliteController();
         mSatelliteController2.countOfSatelliteServiceEnablementsSuccess = 2 + 1;
@@ -1177,6 +1182,11 @@
         mSatelliteController2.countOfDatagramTypeKeepAliveFail = 5;
         mSatelliteController2.isProvisioned = false;
         mSatelliteController2.carrierId = 10;
+        mSatelliteController2.countOfSuccessfulLocationQueries = 30;
+        mSatelliteController2.countOfFailedLocationQueries = 50;
+        mSatelliteController2.countOfP2PSmsAvailableNotificationShown = 30;
+        mSatelliteController2.countOfP2PSmsAvailableNotificationRemoved = 50;
+        mSatelliteController2.isNtnOnlyCarrier = true;
 
         // SatelliteController atom has one data point
         mSatelliteControllers =
@@ -1205,6 +1215,8 @@
         mSatelliteSession1.countOfSatelliteNotificationDisplayed = 4;
         mSatelliteSession1.countOfAutoExitDueToScreenOff = 6;
         mSatelliteSession1.countOfAutoExitDueToTnNetwork = 7;
+        mSatelliteSession1.isEmergency = true;
+        mSatelliteSession1.maxInactivityDurationSec = 8;
 
         mSatelliteSession2 = new SatelliteSession();
         mSatelliteSession2.satelliteServiceInitializationResult =
@@ -1227,6 +1239,8 @@
         mSatelliteSession2.countOfSatelliteNotificationDisplayed = 40;
         mSatelliteSession2.countOfAutoExitDueToScreenOff = 60;
         mSatelliteSession2.countOfAutoExitDueToTnNetwork = 70;
+        mSatelliteSession2.isEmergency = false;
+        mSatelliteSession2.maxInactivityDurationSec = 80;
 
         mSatelliteSessions =
                 new SatelliteSession[] {
@@ -4370,94 +4384,233 @@
     public void addSatelliteControllerStats_withExistingEntries() throws Exception {
         createEmptyTestFile();
         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
-        mPersistAtomsStorage.addSatelliteControllerStats(mSatelliteController1);
-        mPersistAtomsStorage.addSatelliteControllerStats(mSatelliteController2);
+        mPersistAtomsStorage.addSatelliteControllerStats(copyOf(mSatelliteController1));
+        mPersistAtomsStorage.addSatelliteControllerStats(copyOf(mSatelliteController1));
         mPersistAtomsStorage.incTimeMillis(100L);
 
         SatelliteController expected = new SatelliteController();
         expected.countOfSatelliteServiceEnablementsSuccess =
-                mSatelliteController1.countOfSatelliteServiceEnablementsSuccess
-                        + mSatelliteController2.countOfSatelliteServiceEnablementsSuccess;
+                mSatelliteController1.countOfSatelliteServiceEnablementsSuccess * 2;
         expected.countOfSatelliteServiceEnablementsFail =
-                mSatelliteController1.countOfSatelliteServiceEnablementsFail
-                        + mSatelliteController2.countOfSatelliteServiceEnablementsFail;
+                mSatelliteController1.countOfSatelliteServiceEnablementsFail * 2;
         expected.countOfOutgoingDatagramSuccess =
-                mSatelliteController1.countOfOutgoingDatagramSuccess
-                        + mSatelliteController2.countOfOutgoingDatagramSuccess;
+                mSatelliteController1.countOfOutgoingDatagramSuccess * 2;
         expected.countOfOutgoingDatagramFail =
-                mSatelliteController1.countOfOutgoingDatagramFail
-                        + mSatelliteController2.countOfOutgoingDatagramFail;
+                mSatelliteController1.countOfOutgoingDatagramFail * 2;
         expected.countOfIncomingDatagramSuccess =
-                mSatelliteController1.countOfIncomingDatagramSuccess
-                        + mSatelliteController2.countOfIncomingDatagramSuccess;
+                mSatelliteController1.countOfIncomingDatagramSuccess * 2;
         expected.countOfIncomingDatagramFail =
-                mSatelliteController1.countOfIncomingDatagramFail
-                        + mSatelliteController2.countOfIncomingDatagramFail;
+                mSatelliteController1.countOfIncomingDatagramFail * 2;
         expected.countOfDatagramTypeSosSmsSuccess =
-                mSatelliteController1.countOfDatagramTypeSosSmsSuccess
-                        + mSatelliteController2.countOfDatagramTypeSosSmsSuccess;
+                mSatelliteController1.countOfDatagramTypeSosSmsSuccess * 2;
         expected.countOfDatagramTypeSosSmsFail =
-                mSatelliteController1.countOfDatagramTypeSosSmsFail
-                        + mSatelliteController2.countOfDatagramTypeSosSmsFail;
+                mSatelliteController1.countOfDatagramTypeSosSmsFail * 2;
         expected.countOfDatagramTypeLocationSharingSuccess =
-                mSatelliteController1.countOfDatagramTypeLocationSharingSuccess
-                        + mSatelliteController2.countOfDatagramTypeLocationSharingSuccess;
+                mSatelliteController1.countOfDatagramTypeLocationSharingSuccess * 2;
         expected.countOfDatagramTypeLocationSharingFail =
-                mSatelliteController1.countOfDatagramTypeLocationSharingFail
-                        + mSatelliteController2.countOfDatagramTypeLocationSharingFail;
+                mSatelliteController1.countOfDatagramTypeLocationSharingFail * 2;
         expected.countOfProvisionSuccess =
-                mSatelliteController1.countOfProvisionSuccess
-                        + mSatelliteController2.countOfProvisionSuccess;
+                mSatelliteController1.countOfProvisionSuccess * 2;
         expected.countOfProvisionFail =
-                mSatelliteController1.countOfProvisionFail
-                        + mSatelliteController2.countOfProvisionFail;
+                mSatelliteController1.countOfProvisionFail * 2;
         expected.countOfDeprovisionSuccess =
-                mSatelliteController1.countOfDeprovisionSuccess
-                        + mSatelliteController2.countOfDeprovisionSuccess;
+                mSatelliteController1.countOfDeprovisionSuccess * 2;
         expected.countOfDeprovisionFail =
-                mSatelliteController1.countOfDeprovisionFail
-                        + mSatelliteController2.countOfDeprovisionFail;
+                mSatelliteController1.countOfDeprovisionFail * 2;
         expected.totalServiceUptimeSec =
-                mSatelliteController1.totalServiceUptimeSec
-                        + mSatelliteController2.totalServiceUptimeSec;
+                mSatelliteController1.totalServiceUptimeSec * 2;
         expected.totalBatteryConsumptionPercent =
-                mSatelliteController1.totalBatteryConsumptionPercent
-                        + mSatelliteController2.totalBatteryConsumptionPercent;
+                mSatelliteController1.totalBatteryConsumptionPercent * 2;
         expected.totalBatteryChargedTimeSec =
-                mSatelliteController1.totalBatteryChargedTimeSec
-                        + mSatelliteController2.totalBatteryChargedTimeSec;
+                mSatelliteController1.totalBatteryChargedTimeSec * 2;
         expected.countOfDemoModeSatelliteServiceEnablementsSuccess =
-                mSatelliteController1.countOfDemoModeSatelliteServiceEnablementsSuccess
-                        + mSatelliteController2.countOfDemoModeSatelliteServiceEnablementsSuccess;
+                mSatelliteController1.countOfDemoModeSatelliteServiceEnablementsSuccess * 2;
         expected.countOfDemoModeSatelliteServiceEnablementsFail =
-                mSatelliteController1.countOfDemoModeSatelliteServiceEnablementsFail
-                        + mSatelliteController2.countOfDemoModeSatelliteServiceEnablementsFail;
+                mSatelliteController1.countOfDemoModeSatelliteServiceEnablementsFail * 2;
         expected.countOfDemoModeOutgoingDatagramSuccess =
-                mSatelliteController1.countOfDemoModeOutgoingDatagramSuccess
-                        + mSatelliteController2.countOfDemoModeOutgoingDatagramSuccess;
+                mSatelliteController1.countOfDemoModeOutgoingDatagramSuccess * 2;
         expected.countOfDemoModeOutgoingDatagramFail =
-                mSatelliteController1.countOfDemoModeOutgoingDatagramFail
-                        + mSatelliteController2.countOfDemoModeOutgoingDatagramFail;
+                mSatelliteController1.countOfDemoModeOutgoingDatagramFail * 2;
         expected.countOfDemoModeIncomingDatagramSuccess =
-                mSatelliteController1.countOfDemoModeIncomingDatagramSuccess
-                        + mSatelliteController2.countOfDemoModeIncomingDatagramSuccess;
+                mSatelliteController1.countOfDemoModeIncomingDatagramSuccess * 2;
         expected.countOfDemoModeIncomingDatagramFail =
-                mSatelliteController1.countOfDemoModeIncomingDatagramFail
-                        + mSatelliteController2.countOfDemoModeIncomingDatagramFail;
+                mSatelliteController1.countOfDemoModeIncomingDatagramFail * 2;
         expected.countOfDatagramTypeKeepAliveSuccess =
-                mSatelliteController1.countOfDatagramTypeKeepAliveSuccess
-                        + mSatelliteController2.countOfDatagramTypeKeepAliveSuccess;
+                mSatelliteController1.countOfDatagramTypeKeepAliveSuccess * 2;
         expected.countOfDatagramTypeKeepAliveFail =
-                mSatelliteController1.countOfDatagramTypeKeepAliveFail
-                        + mSatelliteController2.countOfDatagramTypeKeepAliveFail;
-        expected.isProvisioned = mSatelliteController2.isProvisioned;
-        expected.carrierId = mSatelliteController2.carrierId;
+                mSatelliteController1.countOfDatagramTypeKeepAliveFail * 2;
+        expected.isProvisioned = mSatelliteController1.isProvisioned;
+        expected.carrierId = mSatelliteController1.carrierId;
+        expected.countOfSatelliteAllowedStateChangedEvents =
+                mSatelliteController1.countOfSatelliteAllowedStateChangedEvents * 2;
+        expected.countOfSuccessfulLocationQueries =
+                mSatelliteController1.countOfSuccessfulLocationQueries * 2;
+        expected.countOfFailedLocationQueries =
+                mSatelliteController1.countOfFailedLocationQueries * 2;
+        expected.countOfP2PSmsAvailableNotificationShown =
+                mSatelliteController1.countOfP2PSmsAvailableNotificationShown * 2;
+        expected.countOfP2PSmsAvailableNotificationRemoved =
+                mSatelliteController1.countOfP2PSmsAvailableNotificationRemoved * 2;
+        expected.isNtnOnlyCarrier = mSatelliteController1.isNtnOnlyCarrier;
 
         // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
         SatelliteController[] output =
                 mPersistAtomsStorage.getSatelliteControllerStats(0L);
-        assertHasStats(output, expected);
+        assertHasStats(output, expected, 1);
+    }
+
+    @Test
+    public void addSatelliteControllerStats_addNewCarrierId() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteControllerStats(mSatelliteController1);
+        mPersistAtomsStorage.addSatelliteControllerStats(mSatelliteController2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        SatelliteController expected1 = mSatelliteController1;
+        SatelliteController expected2 = mSatelliteController2;
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteController[] output =
+                mPersistAtomsStorage.getSatelliteControllerStats(0L);
+
+        assertHasStats(output, expected1, 1);
+        assertHasStats(output, expected2, 1);
+    }
+
+    @Test
+    public void addSatelliteControllerStats_addNewProvisionId() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteControllerStats(mSatelliteController1);
+        mPersistAtomsStorage.addSatelliteControllerStats(mSatelliteController2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        SatelliteController expected1 = mSatelliteController1;
+        SatelliteController expected2 = mSatelliteController2;
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteController[] output =
+                mPersistAtomsStorage.getSatelliteControllerStats(0L);
+
+        assertHasStats(output, expected1, 1);
+        assertHasStats(output, expected2, 1);
+    }
+
+    @Test
+    public void addSatelliteControllerStats_addSameDimension() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        SatelliteController satelliteController1 = copyOf(mSatelliteController1);
+        SatelliteController satelliteController2 = copyOf(mSatelliteController2);
+
+        // set same provisioned and carrier id so that they are in same dimension.
+        satelliteController1.isProvisioned = true;
+        satelliteController2.isProvisioned = true;
+        satelliteController1.carrierId = 1589;
+        satelliteController2.carrierId = 1589;
+        satelliteController1.isNtnOnlyCarrier = false;
+        satelliteController2.isNtnOnlyCarrier = false;
+
+        mPersistAtomsStorage.addSatelliteControllerStats(copyOf(satelliteController1));
+        mPersistAtomsStorage.addSatelliteControllerStats(copyOf(satelliteController2));
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        SatelliteController expected = new SatelliteController();
+        expected.countOfSatelliteServiceEnablementsSuccess =
+                satelliteController1.countOfSatelliteServiceEnablementsSuccess
+                        + satelliteController2.countOfSatelliteServiceEnablementsSuccess;
+        expected.countOfSatelliteServiceEnablementsFail =
+                satelliteController1.countOfSatelliteServiceEnablementsFail
+                        + satelliteController2.countOfSatelliteServiceEnablementsFail;
+        expected.countOfOutgoingDatagramSuccess =
+                satelliteController1.countOfOutgoingDatagramSuccess
+                        + satelliteController2.countOfOutgoingDatagramSuccess;
+        expected.countOfOutgoingDatagramFail = satelliteController1.countOfOutgoingDatagramFail
+                + satelliteController2.countOfOutgoingDatagramFail;
+        expected.countOfIncomingDatagramSuccess =
+                satelliteController1.countOfIncomingDatagramSuccess
+                        + satelliteController2.countOfIncomingDatagramSuccess;
+        expected.countOfIncomingDatagramFail = satelliteController1.countOfIncomingDatagramFail
+                + satelliteController2.countOfIncomingDatagramFail;
+        expected.countOfDatagramTypeSosSmsSuccess =
+                satelliteController1.countOfDatagramTypeSosSmsSuccess
+                        + satelliteController2.countOfDatagramTypeSosSmsSuccess;
+        expected.countOfDatagramTypeSosSmsFail = satelliteController1.countOfDatagramTypeSosSmsFail
+                + satelliteController2.countOfDatagramTypeSosSmsFail;
+        expected.countOfDatagramTypeLocationSharingSuccess =
+                satelliteController1.countOfDatagramTypeLocationSharingSuccess
+                        + satelliteController2.countOfDatagramTypeLocationSharingSuccess;
+        expected.countOfDatagramTypeLocationSharingFail =
+                satelliteController1.countOfDatagramTypeLocationSharingFail
+                        + satelliteController2.countOfDatagramTypeLocationSharingFail;
+        expected.countOfProvisionSuccess = satelliteController1.countOfProvisionSuccess
+                + satelliteController2.countOfProvisionSuccess;
+        expected.countOfProvisionFail = satelliteController1.countOfProvisionFail
+                + satelliteController2.countOfProvisionFail;
+        expected.countOfDeprovisionSuccess = satelliteController1.countOfDeprovisionSuccess
+                + satelliteController2.countOfDeprovisionSuccess;
+        expected.countOfDeprovisionFail = satelliteController1.countOfDeprovisionFail
+                + satelliteController2.countOfDeprovisionFail;
+        expected.totalServiceUptimeSec = satelliteController1.totalServiceUptimeSec
+                + satelliteController2.totalServiceUptimeSec;
+        expected.totalBatteryConsumptionPercent =
+                satelliteController1.totalBatteryConsumptionPercent
+                        + satelliteController2.totalBatteryConsumptionPercent;
+        expected.totalBatteryChargedTimeSec = satelliteController1.totalBatteryChargedTimeSec
+                + satelliteController2.totalBatteryChargedTimeSec;
+        expected.countOfDemoModeSatelliteServiceEnablementsSuccess =
+                satelliteController1.countOfDemoModeSatelliteServiceEnablementsSuccess
+                        + satelliteController2.countOfDemoModeSatelliteServiceEnablementsSuccess;
+        expected.countOfDemoModeSatelliteServiceEnablementsFail =
+                satelliteController1.countOfDemoModeSatelliteServiceEnablementsFail
+                        + satelliteController2.countOfDemoModeSatelliteServiceEnablementsFail;
+        expected.countOfDemoModeOutgoingDatagramSuccess =
+                satelliteController1.countOfDemoModeOutgoingDatagramSuccess
+                        + satelliteController2.countOfDemoModeOutgoingDatagramSuccess;
+        expected.countOfDemoModeOutgoingDatagramFail =
+                satelliteController1.countOfDemoModeOutgoingDatagramFail
+                        + satelliteController2.countOfDemoModeOutgoingDatagramFail;
+        expected.countOfDemoModeIncomingDatagramSuccess =
+                satelliteController1.countOfDemoModeIncomingDatagramSuccess
+                        + satelliteController2.countOfDemoModeIncomingDatagramSuccess;
+        expected.countOfDemoModeIncomingDatagramFail =
+                satelliteController1.countOfDemoModeIncomingDatagramFail
+                        + satelliteController2.countOfDemoModeIncomingDatagramFail;
+        expected.countOfDatagramTypeKeepAliveSuccess =
+                satelliteController1.countOfDatagramTypeKeepAliveSuccess
+                        + satelliteController2.countOfDatagramTypeKeepAliveSuccess;
+        expected.countOfDatagramTypeKeepAliveFail =
+                satelliteController1.countOfDatagramTypeKeepAliveFail
+                        + satelliteController2.countOfDatagramTypeKeepAliveFail;
+        expected.isProvisioned = true;
+        expected.carrierId = 1589;
+        expected.countOfSatelliteAllowedStateChangedEvents =
+                satelliteController1.countOfSatelliteAllowedStateChangedEvents
+                        + satelliteController2.countOfSatelliteAllowedStateChangedEvents;
+        expected.countOfSuccessfulLocationQueries =
+                satelliteController1.countOfSuccessfulLocationQueries
+                        + satelliteController2.countOfSuccessfulLocationQueries;
+        expected.countOfFailedLocationQueries = satelliteController1.countOfFailedLocationQueries
+                + satelliteController2.countOfFailedLocationQueries;
+        expected.countOfP2PSmsAvailableNotificationShown =
+                satelliteController1.countOfP2PSmsAvailableNotificationShown
+                        + satelliteController2.countOfP2PSmsAvailableNotificationShown;
+        expected.countOfP2PSmsAvailableNotificationRemoved =
+                satelliteController1.countOfP2PSmsAvailableNotificationRemoved
+                        + satelliteController2.countOfP2PSmsAvailableNotificationRemoved;
+        expected.isNtnOnlyCarrier = false;
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteController[] output =
+                mPersistAtomsStorage.getSatelliteControllerStats(0L);
+
+        assertHasStats(output, expected, 1);
     }
 
     @Test
@@ -4974,43 +5127,56 @@
     }
 
     @Test
-    public void addCarrierRoamingSatelliteControllerStats_withExistingEntries() throws Exception {
+    public void addCarrierRoamingSatelliteControllerStats_withExistingCarrierId() throws Exception {
         createEmptyTestFile();
         mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
         mPersistAtomsStorage.addCarrierRoamingSatelliteControllerStats(
-                mCarrierRoamingSatelliteControllerStats1);
+                copyOf(mCarrierRoamingSatelliteControllerStats1));
         mPersistAtomsStorage.addCarrierRoamingSatelliteControllerStats(
-                mCarrierRoamingSatelliteControllerStats2);
+                copyOf(mCarrierRoamingSatelliteControllerStats1));
         mPersistAtomsStorage.incTimeMillis(100L);
 
         CarrierRoamingSatelliteControllerStats expected =
                 new CarrierRoamingSatelliteControllerStats();
-        expected.configDataSource = mCarrierRoamingSatelliteControllerStats2.configDataSource;
+        expected.configDataSource = mCarrierRoamingSatelliteControllerStats1.configDataSource;
         expected.countOfEntitlementStatusQueryRequest =
-                mCarrierRoamingSatelliteControllerStats1.countOfEntitlementStatusQueryRequest
-                        + mCarrierRoamingSatelliteControllerStats2
-                        .countOfEntitlementStatusQueryRequest;
+                mCarrierRoamingSatelliteControllerStats1.countOfEntitlementStatusQueryRequest * 2;
         expected.countOfSatelliteConfigUpdateRequest =
-                mCarrierRoamingSatelliteControllerStats1.countOfSatelliteConfigUpdateRequest
-                        + mCarrierRoamingSatelliteControllerStats2
-                        .countOfSatelliteConfigUpdateRequest;
+                mCarrierRoamingSatelliteControllerStats1.countOfSatelliteConfigUpdateRequest * 2;
         expected.countOfSatelliteNotificationDisplayed =
-                mCarrierRoamingSatelliteControllerStats1.countOfSatelliteNotificationDisplayed
-                + mCarrierRoamingSatelliteControllerStats2
-                        .countOfSatelliteNotificationDisplayed;
+                mCarrierRoamingSatelliteControllerStats1.countOfSatelliteNotificationDisplayed * 2;
         expected.satelliteSessionGapMinSec =
-                mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapMinSec;
+                mCarrierRoamingSatelliteControllerStats1.satelliteSessionGapMinSec;
         expected.satelliteSessionGapAvgSec =
-                mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapAvgSec;
+                mCarrierRoamingSatelliteControllerStats1.satelliteSessionGapAvgSec;
         expected.satelliteSessionGapMaxSec =
-                mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapMaxSec;
-        expected.carrierId = mCarrierRoamingSatelliteControllerStats2.carrierId;
-        expected.isDeviceEntitled = mCarrierRoamingSatelliteControllerStats2.isDeviceEntitled;
-
+                mCarrierRoamingSatelliteControllerStats1.satelliteSessionGapMaxSec;
+        expected.carrierId = mCarrierRoamingSatelliteControllerStats1.carrierId;
+        expected.isDeviceEntitled = mCarrierRoamingSatelliteControllerStats1.isDeviceEntitled;
         verifyCurrentStateSavedToFileOnce();
         CarrierRoamingSatelliteControllerStats[] output =
                 mPersistAtomsStorage.getCarrierRoamingSatelliteControllerStats(0L);
-        assertHasStats(output, expected);
+        assertHasStats(output, expected, 1);
+    }
+
+    @Test
+    public void addCarrierRoamingSatelliteControllerStats_addNewCarrierId() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addCarrierRoamingSatelliteControllerStats(
+                copyOf(mCarrierRoamingSatelliteControllerStats1));
+        mPersistAtomsStorage.addCarrierRoamingSatelliteControllerStats(
+                copyOf(mCarrierRoamingSatelliteControllerStats2));
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        CarrierRoamingSatelliteControllerStats expected1 = mCarrierRoamingSatelliteControllerStats1;
+        CarrierRoamingSatelliteControllerStats expected2 = mCarrierRoamingSatelliteControllerStats2;
+
+        CarrierRoamingSatelliteControllerStats[] output =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteControllerStats(0L);
+
+        assertHasStats(output, expected1, 1);
+        assertHasStats(output, expected2, 1);
     }
 
     @Test
@@ -5816,60 +5982,77 @@
         assertEquals(expectedCount, actualCount);
     }
 
-    private static void assertHasStats(
-            SatelliteController[] tested,
-            @Nullable SatelliteController expectedStats) {
+    private static void assertHasStats(SatelliteController[] tested,
+            @Nullable SatelliteController expectedStats, int expectedCount) {
         assertNotNull(tested);
-        assertEquals(tested[0].countOfSatelliteServiceEnablementsSuccess,
-                expectedStats.countOfSatelliteServiceEnablementsSuccess);
-        assertEquals(tested[0].countOfSatelliteServiceEnablementsFail,
-                expectedStats.countOfSatelliteServiceEnablementsFail);
-        assertEquals(tested[0].countOfOutgoingDatagramSuccess,
-                expectedStats.countOfOutgoingDatagramSuccess);
-        assertEquals(tested[0].countOfOutgoingDatagramFail,
-                expectedStats.countOfOutgoingDatagramFail);
-        assertEquals(tested[0].countOfIncomingDatagramSuccess,
-                expectedStats.countOfIncomingDatagramSuccess);
-        assertEquals(tested[0].countOfIncomingDatagramFail,
-                expectedStats.countOfIncomingDatagramFail);
-        assertEquals(tested[0].countOfDatagramTypeSosSmsSuccess,
-                expectedStats.countOfDatagramTypeSosSmsSuccess);
-        assertEquals(tested[0].countOfDatagramTypeSosSmsFail,
-                expectedStats.countOfDatagramTypeSosSmsFail);
-        assertEquals(tested[0].countOfDatagramTypeLocationSharingSuccess,
-                expectedStats.countOfDatagramTypeLocationSharingSuccess);
-        assertEquals(tested[0].countOfDatagramTypeLocationSharingFail,
-                expectedStats.countOfDatagramTypeLocationSharingFail);
-        assertEquals(tested[0].totalServiceUptimeSec,
-                expectedStats.totalServiceUptimeSec);
-        assertEquals(tested[0].totalBatteryConsumptionPercent,
-                expectedStats.totalBatteryConsumptionPercent);
-        assertEquals(tested[0].totalBatteryChargedTimeSec,
-                expectedStats.totalBatteryChargedTimeSec);
-        assertEquals(tested[0].countOfDemoModeSatelliteServiceEnablementsSuccess,
-                expectedStats.countOfDemoModeSatelliteServiceEnablementsSuccess);
-        assertEquals(tested[0].countOfDemoModeSatelliteServiceEnablementsFail,
-                expectedStats.countOfDemoModeSatelliteServiceEnablementsFail);
-        assertEquals(tested[0].countOfDemoModeOutgoingDatagramSuccess,
-                expectedStats.countOfDemoModeOutgoingDatagramSuccess);
-        assertEquals(tested[0].countOfDemoModeOutgoingDatagramFail,
-                expectedStats.countOfDemoModeOutgoingDatagramFail);
-        assertEquals(tested[0].countOfDemoModeIncomingDatagramSuccess,
-                expectedStats.countOfDemoModeIncomingDatagramSuccess);
-        assertEquals(tested[0].countOfDemoModeIncomingDatagramFail,
-                expectedStats.countOfDemoModeIncomingDatagramFail);
-        assertEquals(tested[0].countOfDatagramTypeKeepAliveSuccess,
-                expectedStats.countOfDatagramTypeKeepAliveSuccess);
-        assertEquals(tested[0].countOfDatagramTypeKeepAliveFail,
-                expectedStats.countOfDatagramTypeKeepAliveFail);
-        assertEquals(tested[0].countOfAllowedSatelliteAccess,
-                expectedStats.countOfAllowedSatelliteAccess);
-        assertEquals(tested[0].countOfDisallowedSatelliteAccess,
-                expectedStats.countOfDisallowedSatelliteAccess);
-        assertEquals(tested[0].countOfSatelliteAccessCheckFail,
-                expectedStats.countOfSatelliteAccessCheckFail);
-        assertEquals(tested[0].isProvisioned, expectedStats.isProvisioned);
-        assertEquals(tested[0].carrierId, expectedStats.carrierId);
+        int actualCount = 0;
+        for (SatelliteController stats : tested) {
+            if (expectedStats.carrierId == stats.carrierId
+                    && expectedStats.isProvisioned == stats.isProvisioned
+                    && expectedStats.isNtnOnlyCarrier == stats.isNtnOnlyCarrier) {
+                assertEquals(expectedStats.countOfSatelliteServiceEnablementsSuccess,
+                        stats.countOfSatelliteServiceEnablementsSuccess);
+                assertEquals(expectedStats.countOfSatelliteServiceEnablementsFail,
+                        stats.countOfSatelliteServiceEnablementsFail);
+                assertEquals(expectedStats.countOfOutgoingDatagramSuccess,
+                        stats.countOfOutgoingDatagramSuccess);
+                assertEquals(expectedStats.countOfOutgoingDatagramFail,
+                        stats.countOfOutgoingDatagramFail);
+                assertEquals(expectedStats.countOfIncomingDatagramSuccess,
+                        stats.countOfIncomingDatagramSuccess);
+                assertEquals(expectedStats.countOfIncomingDatagramFail,
+                        stats.countOfIncomingDatagramFail);
+                assertEquals(expectedStats.countOfDatagramTypeSosSmsSuccess,
+                        stats.countOfDatagramTypeSosSmsSuccess);
+                assertEquals(expectedStats.countOfDatagramTypeSosSmsFail,
+                        stats.countOfDatagramTypeSosSmsFail);
+                assertEquals(expectedStats.countOfDatagramTypeLocationSharingSuccess,
+                        stats.countOfDatagramTypeLocationSharingSuccess);
+                assertEquals(expectedStats.countOfDatagramTypeLocationSharingFail,
+                        stats.countOfDatagramTypeLocationSharingFail);
+                assertEquals(expectedStats.totalServiceUptimeSec, stats.totalServiceUptimeSec);
+                assertEquals(expectedStats.totalBatteryConsumptionPercent,
+                        stats.totalBatteryConsumptionPercent);
+                assertEquals(expectedStats.totalBatteryChargedTimeSec,
+                        stats.totalBatteryChargedTimeSec);
+                assertEquals(expectedStats.countOfDemoModeSatelliteServiceEnablementsSuccess,
+                        stats.countOfDemoModeSatelliteServiceEnablementsSuccess);
+                assertEquals(expectedStats.countOfDemoModeSatelliteServiceEnablementsFail,
+                        stats.countOfDemoModeSatelliteServiceEnablementsFail);
+                assertEquals(expectedStats.countOfDemoModeOutgoingDatagramSuccess,
+                        stats.countOfDemoModeOutgoingDatagramSuccess);
+                assertEquals(expectedStats.countOfDemoModeOutgoingDatagramFail,
+                        stats.countOfDemoModeOutgoingDatagramFail);
+                assertEquals(expectedStats.countOfDemoModeIncomingDatagramSuccess,
+                        stats.countOfDemoModeIncomingDatagramSuccess);
+                assertEquals(expectedStats.countOfDemoModeIncomingDatagramFail,
+                        stats.countOfDemoModeIncomingDatagramFail);
+                assertEquals(expectedStats.countOfDatagramTypeKeepAliveSuccess,
+                        stats.countOfDatagramTypeKeepAliveSuccess);
+                assertEquals(expectedStats.countOfDatagramTypeKeepAliveFail,
+                        stats.countOfDatagramTypeKeepAliveFail);
+                assertEquals(expectedStats.countOfAllowedSatelliteAccess,
+                        stats.countOfAllowedSatelliteAccess);
+                assertEquals(expectedStats.countOfDisallowedSatelliteAccess,
+                        stats.countOfDisallowedSatelliteAccess);
+                assertEquals(expectedStats.countOfSatelliteAccessCheckFail,
+                        stats.countOfSatelliteAccessCheckFail);
+                assertEquals(expectedStats.isProvisioned, stats.isProvisioned);
+                assertEquals(expectedStats.carrierId, stats.carrierId);
+                assertEquals(expectedStats.countOfSatelliteAllowedStateChangedEvents,
+                        stats.countOfSatelliteAllowedStateChangedEvents);
+                assertEquals(expectedStats.countOfSuccessfulLocationQueries,
+                        stats.countOfSuccessfulLocationQueries);
+                assertEquals(expectedStats.countOfFailedLocationQueries,
+                        stats.countOfFailedLocationQueries);
+                assertEquals(expectedStats.countOfP2PSmsAvailableNotificationShown,
+                        stats.countOfP2PSmsAvailableNotificationShown);
+                assertEquals(expectedStats.countOfP2PSmsAvailableNotificationRemoved,
+                        stats.countOfP2PSmsAvailableNotificationRemoved);
+                actualCount++;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
     }
 
     private static void assertHasStatsAndCount(
@@ -5879,31 +6062,33 @@
         int actualCount = 0;
         for (SatelliteSession stats : tested) {
             if (stats.satelliteServiceInitializationResult
-                    == expectedStats.satelliteServiceInitializationResult
+                            == expectedStats.satelliteServiceInitializationResult
                     && stats.satelliteTechnology == expectedStats.satelliteTechnology
                     && stats.satelliteServiceTerminationResult
-                        == expectedStats.satelliteServiceTerminationResult
+                            == expectedStats.satelliteServiceTerminationResult
                     && stats.initializationProcessingTimeMillis
-                        == expectedStats.initializationProcessingTimeMillis
+                            == expectedStats.initializationProcessingTimeMillis
                     && stats.terminationProcessingTimeMillis
-                        == expectedStats.terminationProcessingTimeMillis
+                            == expectedStats.terminationProcessingTimeMillis
                     && stats.sessionDurationSeconds == expectedStats.sessionDurationSeconds
                     && stats.countOfOutgoingDatagramSuccess
-                        == expectedStats.countOfOutgoingDatagramSuccess
+                            == expectedStats.countOfOutgoingDatagramSuccess
                     && stats.countOfOutgoingDatagramFailed
-                        == expectedStats.countOfOutgoingDatagramFailed
+                            == expectedStats.countOfOutgoingDatagramFailed
                     && stats.countOfIncomingDatagramSuccess
-                        == expectedStats.countOfIncomingDatagramSuccess
+                            == expectedStats.countOfIncomingDatagramSuccess
                     && stats.countOfIncomingDatagramFailed
-                        == expectedStats.countOfIncomingDatagramFailed
+                            == expectedStats.countOfIncomingDatagramFailed
                     && stats.isDemoMode == expectedStats.isDemoMode
                     && stats.carrierId == expectedStats.carrierId
                     && stats.countOfSatelliteNotificationDisplayed
-                    == expectedStats.countOfSatelliteNotificationDisplayed
+                            == expectedStats.countOfSatelliteNotificationDisplayed
                     && stats.countOfAutoExitDueToScreenOff
-                    == expectedStats.countOfAutoExitDueToScreenOff
+                            == expectedStats.countOfAutoExitDueToScreenOff
                     && stats.countOfAutoExitDueToTnNetwork
-                    == expectedStats.countOfAutoExitDueToTnNetwork) {
+                            == expectedStats.countOfAutoExitDueToTnNetwork
+                    && stats.isEmergency == expectedStats.isEmergency
+                    && stats.maxInactivityDurationSec == expectedStats.maxInactivityDurationSec) {
                 actualCount = stats.count;
             }
         }
@@ -6251,20 +6436,29 @@
     }
 
     private static void assertHasStats(CarrierRoamingSatelliteControllerStats[] tested,
-            @Nullable CarrierRoamingSatelliteControllerStats expectedStats) {
+            @Nullable CarrierRoamingSatelliteControllerStats expectedStats, int expectedCount) {
         assertNotNull(tested);
-        assertEquals(tested[0].configDataSource, expectedStats.configDataSource);
-        assertEquals(tested[0].countOfEntitlementStatusQueryRequest,
-                expectedStats.countOfEntitlementStatusQueryRequest);
-        assertEquals(tested[0].countOfSatelliteConfigUpdateRequest,
-                expectedStats.countOfSatelliteConfigUpdateRequest);
-        assertEquals(tested[0].countOfSatelliteNotificationDisplayed,
-                expectedStats.countOfSatelliteNotificationDisplayed);
-        assertEquals(tested[0].satelliteSessionGapMinSec, expectedStats.satelliteSessionGapMinSec);
-        assertEquals(tested[0].satelliteSessionGapAvgSec, expectedStats.satelliteSessionGapAvgSec);
-        assertEquals(tested[0].satelliteSessionGapMaxSec, expectedStats.satelliteSessionGapMaxSec);
-        assertEquals(tested[0].carrierId, expectedStats.carrierId);
-        assertEquals(tested[0].isDeviceEntitled, expectedStats.isDeviceEntitled);
+        int count = 0;
+        for (CarrierRoamingSatelliteControllerStats stats : tested) {
+            if (expectedStats.carrierId == stats.carrierId) {
+                assertEquals(expectedStats.configDataSource, stats.configDataSource);
+                assertEquals(expectedStats.countOfEntitlementStatusQueryRequest,
+                        stats.countOfEntitlementStatusQueryRequest);
+                assertEquals(expectedStats.countOfSatelliteConfigUpdateRequest,
+                        stats.countOfSatelliteConfigUpdateRequest);
+                assertEquals(expectedStats.countOfSatelliteNotificationDisplayed,
+                        stats.countOfSatelliteNotificationDisplayed);
+                assertEquals(expectedStats.satelliteSessionGapMinSec,
+                        stats.satelliteSessionGapMinSec);
+                assertEquals(expectedStats.satelliteSessionGapAvgSec,
+                        stats.satelliteSessionGapAvgSec);
+                assertEquals(expectedStats.satelliteSessionGapMaxSec,
+                        stats.satelliteSessionGapMaxSec);
+                assertEquals(expectedStats.isDeviceEntitled, stats.isDeviceEntitled);
+                count++;
+            }
+        }
+        assertEquals(expectedCount, count);
     }
 
     private static void assertHasStatsAndCount(
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
index 5740336..e981e88 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
@@ -107,6 +107,9 @@
                         .setCountOfDisallowedSatelliteAccess(2)
                         .setCountOfSatelliteAccessCheckFail(3)
                         .setIsProvisioned(true)
+                        .setCountOfP2PSmsAvailableNotificationShown(3)
+                        .setCountOfP2PSmsAvailableNotificationRemoved(3)
+                        .setIsNtnOnlyCarrier(false)
                         .build();
 
         mSatelliteStats.onSatelliteControllerMetrics(param);
@@ -170,7 +173,14 @@
                 stats.countOfDisallowedSatelliteAccess);
         assertEquals(param.getCountOfSatelliteAccessCheckFail(),
                 stats.countOfSatelliteAccessCheckFail);
-        assertEquals(param.isProvisioned(), stats.isProvisioned);
+        assertEquals(SatelliteStats.SatelliteControllerParams.isProvisioned(), stats.isProvisioned);
+        assertEquals(SatelliteStats.SatelliteControllerParams.getCarrierId(), stats.carrierId);
+        assertEquals(param.getCountOfP2PSmsAvailableNotificationShown(),
+                stats.countOfP2PSmsAvailableNotificationShown);
+        assertEquals(param.getCountOfP2PSmsAvailableNotificationRemoved(),
+                stats.countOfP2PSmsAvailableNotificationRemoved);
+        assertEquals(SatelliteStats.SatelliteControllerParams.isNtnOnlyCarrier(),
+                stats.isNtnOnlyCarrier);
 
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
@@ -239,6 +249,12 @@
                         .setCountOfIncomingDatagramFailed(0)
                         .setIsDemoMode(false)
                         .setMaxNtnSignalStrengthLevel(NTN_SIGNAL_STRENGTH_GOOD)
+                        .setCarrierId(1589)
+                        .setCountOfSatelliteNotificationDisplayed(5)
+                        .setCountOfAutoExitDueToScreenOff(7)
+                        .setCountOfAutoExitDueToTnNetwork(3)
+                        .setIsEmergency(true)
+                        .setMaxInactivityDurationSec(10)
                         .build();
 
         mSatelliteStats.onSatelliteSessionMetrics(param);
@@ -262,6 +278,13 @@
         assertEquals(param.getCountOfIncomingDatagramFailed(), stats.countOfIncomingDatagramFailed);
         assertEquals(param.getIsDemoMode(), stats.isDemoMode);
         assertEquals(param.getMaxNtnSignalStrengthLevel(), stats.maxNtnSignalStrengthLevel);
+        assertEquals(param.getCarrierId(), stats.carrierId);
+        assertEquals(param.getCountOfSatelliteNotificationDisplayed(),
+                stats.countOfSatelliteNotificationDisplayed);
+        assertEquals(param.getCountOfAutoExitDueToScreenOff(), stats.countOfAutoExitDueToScreenOff);
+        assertEquals(param.getCountOfAutoExitDueToTnNetwork(), stats.countOfAutoExitDueToTnNetwork);
+        assertEquals(param.getIsEmergency(), stats.isEmergency);
+        assertEquals(param.getMaxInactivityDurationSec(), stats.maxInactivityDurationSec);
 
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java
index 8173bbc..fdb94c9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java
@@ -180,7 +180,7 @@
         assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
         assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
         assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
-        assertEquals(10, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
         assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
         assertEquals(0, mTestStats.mCountOfProvisionSuccess);
         assertEquals(0, mTestStats.mCountOfProvisionFail);
@@ -282,7 +282,7 @@
         assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
         assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
         assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
-        assertEquals(10, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
         assertEquals(0, mTestStats.mCountOfProvisionSuccess);
         assertEquals(0, mTestStats.mCountOfProvisionFail);
         assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramControllerTest.java
index 2961b4d..271e0ad 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramControllerTest.java
@@ -16,10 +16,12 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS;
 import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE;
 import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED;
 import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP;
 import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING;
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_SMS;
 import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE;
 import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_UNKNOWN;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
@@ -44,6 +46,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import com.android.internal.R;
 import com.android.internal.telephony.TelephonyTest;
 
 import org.junit.After;
@@ -92,8 +95,8 @@
         // Move both send and receive to IDLE state
         mDatagramControllerUT.updateSendStatus(SUB_ID, DATAGRAM_TYPE_UNKNOWN,
                 SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, 0, SATELLITE_RESULT_SUCCESS);
-        mDatagramControllerUT.updateReceiveStatus(SUB_ID, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, 0,
-                SATELLITE_RESULT_SUCCESS);
+        mDatagramControllerUT.updateReceiveStatus(SUB_ID, DATAGRAM_TYPE_SOS_MESSAGE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, 0, SATELLITE_RESULT_SUCCESS);
         pushDemoModeSosDatagram(DATAGRAM_TYPE_SOS_MESSAGE);
     }
 
@@ -187,6 +190,76 @@
         }
     }
 
+    @Test
+    public void testNeedsWaitingForSatelliteConnected_checkMessageInNotConnected_returnsFalse()
+            throws Exception {
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mContextFixture.putBooleanResource(
+                R.bool.config_satellite_allow_check_message_in_not_connected, true);
+        mDatagramControllerUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+
+        boolean result =
+                mDatagramControllerUT.needsWaitingForSatelliteConnected(
+                        DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS);
+
+        assertFalse(result);
+    }
+
+    @Test
+    public void testNeedsWaitingForSatelliteConnected_regularSmsInNotConnected_returnsTrue()
+            throws Exception {
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mContextFixture.putBooleanResource(
+                R.bool.config_satellite_allow_check_message_in_not_connected, true);
+        mDatagramControllerUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+
+        boolean result =
+                mDatagramControllerUT.needsWaitingForSatelliteConnected(
+                        DATAGRAM_TYPE_SMS);
+
+        assertTrue(result);
+    }
+
+    @Test
+    public void
+            testNeedsWaitingForSatelliteConnected_checkMessageInNotConnected_allowCheckMessageFalse_returnsTrue()
+                    throws Exception {
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mContextFixture.putBooleanResource(
+                R.bool.config_satellite_allow_check_message_in_not_connected, false);
+        mDatagramControllerUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+
+        boolean result =
+                mDatagramControllerUT.needsWaitingForSatelliteConnected(
+                        DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS);
+
+        assertTrue(result);
+    }
+
+    @Test
+    public void
+            testNeedsWaitingForSatelliteConnected_checkMessageInNotConnected_carrierRoamingNbIotNtnFalse_returnsTrue()
+                    throws Exception {
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(false);
+        mContextFixture.putBooleanResource(
+                R.bool.config_satellite_allow_check_message_in_not_connected, true);
+        mDatagramControllerUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+
+        boolean result =
+                mDatagramControllerUT.needsWaitingForSatelliteConnected(
+                        DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS);
+
+        assertTrue(result);
+    }
+
     private void testUpdateSendStatus(boolean isDemoMode, int datagramType, int sendState) {
         mDatagramControllerUT.setDemoMode(isDemoMode);
         clearAllInvocations();
@@ -197,7 +270,7 @@
                 errorCode);
 
         verify(mMockSatelliteSessionController)
-                .onDatagramTransferStateChanged(eq(sendState), anyInt());
+                .onDatagramTransferStateChanged(eq(sendState), anyInt(), anyInt());
         verify(mMockPointingAppController).updateSendDatagramTransferState(
                 eq(SUB_ID), eq(datagramType), eq(sendState), eq(sendPendingCount), eq(errorCode));
 
@@ -219,10 +292,10 @@
         int receivePendingCount = 1;
         int errorCode = SATELLITE_RESULT_SUCCESS;
         mDatagramControllerUT.updateReceiveStatus(
-                SUB_ID, receiveState, receivePendingCount, errorCode);
+                SUB_ID, DATAGRAM_TYPE_SOS_MESSAGE, receiveState, receivePendingCount, errorCode);
 
         verify(mMockSatelliteSessionController)
-                .onDatagramTransferStateChanged(anyInt(), eq(receiveState));
+                .onDatagramTransferStateChanged(anyInt(), eq(receiveState), anyInt());
         verify(mMockPointingAppController).updateReceiveDatagramTransferState(
                 eq(SUB_ID), eq(receiveState), eq(receivePendingCount), eq(errorCode));
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
index 21ee476..b4de672 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
@@ -59,6 +60,7 @@
 import android.testing.TestableLooper;
 
 import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.SmsDispatchersController;
@@ -257,7 +259,7 @@
                             eq(SATELLITE_RESULT_SUCCESS));
             verifyNoMoreInteractions(mMockDatagramController);
             verify(mMockSessionMetricsStats, times(1))
-                    .addCountOfSuccessfulOutgoingDatagram(eq(datagramType));
+                    .addCountOfSuccessfulOutgoingDatagram(eq(datagramType), anyLong());
             verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
                     any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
             assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
@@ -398,7 +400,7 @@
                     any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
             assertThat(mResultListener.peek()).isEqualTo(SATELLITE_RESULT_SUCCESS);
             verify(mMockSessionMetricsStats, times(1))
-                    .addCountOfSuccessfulOutgoingDatagram(anyInt());
+                    .addCountOfSuccessfulOutgoingDatagram(anyInt(), anyLong());
             clearInvocations(mMockSatelliteModemInterface);
             clearInvocations(mMockDatagramController);
             clearInvocations(mMockSessionMetricsStats);
@@ -523,7 +525,7 @@
                             eq(SATELLITE_RESULT_SUCCESS));
             assertThat(mResultListener.peek()).isEqualTo(SATELLITE_RESULT_SUCCESS);
             verify(mMockSessionMetricsStats, times(1))
-                    .addCountOfSuccessfulOutgoingDatagram(eq(datagramType));
+                    .addCountOfSuccessfulOutgoingDatagram(eq(datagramType), anyLong());
             mDatagramDispatcherUT.setDemoMode(false);
             mDatagramDispatcherUT.setDeviceAlignedWithSatellite(false);
         }
@@ -618,7 +620,7 @@
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
                         eq(SATELLITE_RESULT_SUCCESS));
         verify(mMockSessionMetricsStats, times(1))
-                .addCountOfSuccessfulOutgoingDatagram(eq(DATAGRAM_TYPE2));
+                .addCountOfSuccessfulOutgoingDatagram(eq(DATAGRAM_TYPE2), anyLong());
 
         mDatagramDispatcherUT.setDemoMode(false);
         mDatagramDispatcherUT.setDeviceAlignedWithSatellite(false);
@@ -731,7 +733,7 @@
                     anyInt(), any(SatelliteDatagram.class));
             verify(mMockDatagramController).pollPendingSatelliteDatagrams(anyInt(), any());
             verify(mMockSessionMetricsStats, times(1))
-                    .addCountOfSuccessfulOutgoingDatagram(anyInt());
+                    .addCountOfSuccessfulOutgoingDatagram(anyInt(), anyLong());
 
             // Test when overlay config config_send_satellite_datagram_to_modem_in_demo_mode is
             // false
@@ -824,7 +826,7 @@
                         eq(SATELLITE_RESULT_SUCCESS));
         verify(mMockSmsDispatchersController).sendCarrierRoamingNbIotNtnText(eq(mPendingSms));
 
-        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.messageId, true);
+        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId, true);
         processAllMessages();
 
         mInOrder.verify(mMockDatagramController)
@@ -836,6 +838,8 @@
                 .updateSendStatus(eq(SUB_ID), eq(datagramType),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
                         eq(SATELLITE_RESULT_SUCCESS));
+        verify(mMockSessionMetricsStats, times(1))
+                .addCountOfSuccessfulOutgoingDatagram(eq(datagramType), anyLong());
         verifyNoMoreInteractions(mMockDatagramController);
     }
 
@@ -858,7 +862,7 @@
                         eq(SATELLITE_RESULT_SUCCESS));
         verify(mMockSmsDispatchersController).sendCarrierRoamingNbIotNtnText(eq(mPendingSms));
 
-        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.messageId, false);
+        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId, false);
         processAllMessages();
 
         mInOrder.verify(mMockDatagramController)
@@ -872,6 +876,9 @@
                         eq(datagramType),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
                         eq(SATELLITE_RESULT_SUCCESS));
+        verify(mMockSessionMetricsStats, times(1))
+                .addCountOfFailedOutgoingDatagram(eq(datagramType), eq(
+                        SatelliteManager.SATELLITE_RESULT_NETWORK_ERROR));
     }
 
     @Test
@@ -1041,7 +1048,7 @@
                         eq(1),
                         eq(SATELLITE_RESULT_SUCCESS));
         verify(mMockSessionMetricsStats, times(1))
-                .addCountOfSuccessfulOutgoingDatagram(eq(datagramTypeSos));
+                .addCountOfSuccessfulOutgoingDatagram(eq(datagramTypeSos), anyLong());
         verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
                 any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
         assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
@@ -1056,7 +1063,7 @@
                         eq(SATELLITE_RESULT_SUCCESS));
         verify(mMockSmsDispatchersController).sendCarrierRoamingNbIotNtnText(eq(mPendingSms));
 
-        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.messageId, true);
+        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId, true);
         processAllMessages();
 
         mInOrder.verify(mMockDatagramController)
@@ -1107,7 +1114,7 @@
         processAllMessages();
         verifyZeroInteractions(mMockSatelliteModemInterface);
 
-        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.messageId, true);
+        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId, true);
         processAllMessages();
         mInOrder.verify(mMockDatagramController)
                 .updateSendStatus(eq(SUB_ID), eq(datagramTypeSms),
@@ -1133,12 +1140,187 @@
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
                         eq(SATELLITE_RESULT_SUCCESS));
         verify(mMockSessionMetricsStats, times(1))
-                .addCountOfSuccessfulOutgoingDatagram(eq(datagramTypeSos));
+                .addCountOfSuccessfulOutgoingDatagram(eq(datagramTypeSos), anyLong());
         verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
                 any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
         assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
     }
 
+   @Test
+   public void testHandleMessage_eventMtSmsPollingThrottleTimedOut_sendsMtSmsPollInNotConnected() {
+        setShouldPollMtSmsTrue();
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mContextFixture.putBooleanResource(
+                R.bool.config_satellite_allow_check_message_in_not_connected, true);
+
+        mDatagramDispatcherUT.handleMessage(
+                mDatagramDispatcherUT.obtainMessage(10 /*EVENT_MT_SMS_POLLING_THROTTLE_TIMED_OUT*/,
+                        new AsyncResult(null, null, null)));
+
+        verify(mMockSmsDispatchersController, times(1)).sendMtSmsPollingMessage();
+   }
+
+    @Test
+    public void
+            testHandleMessage_eventMtSmsPollingThrottleTimedOut_configDisabled_doesNotSendMtSmsPoll() {
+        setShouldPollMtSmsTrue();
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        // Set config_satellite_allow_check_message_in_not_connected to false
+        mContextFixture.putBooleanResource(
+                R.bool.config_satellite_allow_check_message_in_not_connected, false);
+
+        mDatagramDispatcherUT.handleMessage(
+                mDatagramDispatcherUT.obtainMessage(10 /*EVENT_MT_SMS_POLLING_THROTTLE_TIMED_OUT*/,
+                        new AsyncResult(null, null, null)));
+
+        verifyZeroInteractions(mMockSmsDispatchersController);
+   }
+
+    @Test
+    public void
+            testHandleMessage_eventMtSmsPollingThrottleTimedOut_flagDisabled_doesNotSendMtSmsPoll() {
+        setShouldPollMtSmsTrue();
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(true);
+        // Set flag to false
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(false);
+        mContextFixture.putBooleanResource(
+                R.bool.config_satellite_allow_check_message_in_not_connected, true);
+
+        mDatagramDispatcherUT.handleMessage(
+                mDatagramDispatcherUT.obtainMessage(10 /*EVENT_MT_SMS_POLLING_THROTTLE_TIMED_OUT*/,
+                        new AsyncResult(null, null, null)));
+
+        verifyZeroInteractions(mMockSmsDispatchersController);
+   }
+
+
+
+    @Test
+    public void testSetDeviceAlignedWithSatellite_isAligned_notConnected_sendsMtSmsPoll() {
+        setShouldPollMtSmsTrue();
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mContextFixture.putBooleanResource(
+                R.bool.config_satellite_allow_check_message_in_not_connected, true);
+
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(true);
+
+        verify(mMockSmsDispatchersController, times(1)).sendMtSmsPollingMessage();
+    }
+
+    @Test
+    public void testSetDeviceAlignedWithSatellite_notAligned_doesNotSendsMtSmsPoll() {
+        setShouldPollMtSmsTrue();
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mContextFixture.putBooleanResource(
+                R.bool.config_satellite_allow_check_message_in_not_connected, true);
+
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(false);
+
+        verifyZeroInteractions(mMockSmsDispatchersController);
+    }
+
+    @Test
+    public void testOnSatelliteModemStateChanged_notConnected_sendsMtSmsPoll() {
+        setShouldPollMtSmsTrue();
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mContextFixture.putBooleanResource(
+                R.bool.config_satellite_allow_check_message_in_not_connected, true);
+
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+
+        verify(mMockSmsDispatchersController, times(1)).sendMtSmsPollingMessage();
+    }
+
+    @Test
+    public void testOnSatelliteModemStateChanged_connected_sendsMtSmsPoll() {
+        setShouldPollMtSmsTrue();
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+
+        verify(mMockSmsDispatchersController, times(1)).sendMtSmsPollingMessage();
+    }
+
+    @Test
+    public void testOnSatelliteModemStateChanged_transferring_sendsMtSmsPoll() {
+        setShouldPollMtSmsTrue();
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+
+        verify(mMockSmsDispatchersController, times(1)).sendMtSmsPollingMessage();
+    }
+
+    @Test
+    public void testOnSatelliteModemStateChanged_throttled_doesNotSendMtSmsPoll() {
+        startMtSmsPollingThrottle();
+        setShouldPollMtSmsTrue();
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mContextFixture.putBooleanResource(
+                R.bool.config_satellite_allow_check_message_in_not_connected, true);
+
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+
+        verify(mMockSmsDispatchersController, times(0)).sendMtSmsPollingMessage();
+    }
+
+    @Test
+    public void testOnSatelliteModemStateChanged_onFirstConnected_sendsMtSmsPoll() {
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        // Set the following so shouldPollMtSms returns true
+        mContextFixture.putBooleanResource(R.bool.config_enabled_mt_sms_polling, true);
+        when(mMockSatelliteController.shouldSendSmsToDatagramDispatcher(any(Phone.class)))
+                .thenReturn(true);
+
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+
+        verify(mMockSmsDispatchersController, times(1)).sendMtSmsPollingMessage();
+    }
+
+    private void setModemState(int state) {
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(state);
+    }
+
+    private void setShouldPollMtSmsTrue() {
+        // Set mHasEnteredConnectedState true
+        setModemState(SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        // Set the following so shouldPollMtSms returns true
+        mContextFixture.putBooleanResource(R.bool.config_enabled_mt_sms_polling, true);
+        when(mMockSatelliteController.shouldSendSmsToDatagramDispatcher(any(Phone.class)))
+                .thenReturn(true);
+        // This will trigger mShouldPollMtSms = shouldPollMtSms
+        setModemState(SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+    }
+
+    private void startMtSmsPollingThrottle() {
+        // Call sendSms to put message in mPendingSmsMap with isMtSmsPolling=true
+        PendingRequest pendingRequest = new PendingRequest(
+                SmsDispatchersController.PendingRequest.TYPE_TEXT, null, "test-app",
+                Binder.getCallingUserHandle().getIdentifier(), "1111", "2222", asArrayList(null),
+                asArrayList(null), false, null, 0, asArrayList("text"), null, false, 0, false,
+                10, 100L, false, /* isMtSmsPolling= */ true);
+        mDatagramDispatcherUT.sendSms(pendingRequest);
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = mPhone.getSubId();
+        args.arg2 = pendingRequest.uniqueMessageId;
+        args.arg3 = true;
+        // EVENT_SEND_SMS_DONE to trigger handleEventSendSmsDone which will start the throttle
+        mDatagramDispatcherUT.handleMessage(
+                mDatagramDispatcherUT.obtainMessage(9 /*EVENT_SEND_SMS_DONE*/, args));
+    }
+
     private boolean waitForIntegerConsumerResult(int expectedNumberOfEvents) {
         for (int i = 0; i < expectedNumberOfEvents; i++) {
             try {
@@ -1204,6 +1386,6 @@
                 SmsDispatchersController.PendingRequest.TYPE_TEXT, null, "test-app",
                 Binder.getCallingUserHandle().getIdentifier(), "1111", "2222", asArrayList(null),
                 asArrayList(null), false, null, 0, asArrayList("text"), null, false, 0, false,
-                10, 100L, false);
+                10, 100L, false, false);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
index e964ced..a1f63d0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
@@ -187,6 +187,7 @@
         mInOrder.verify(mMockDatagramController)
                 .needsWaitingForSatelliteConnected(eq(SatelliteManager.DATAGRAM_TYPE_UNKNOWN));
         mInOrder.verify(mMockDatagramController).updateReceiveStatus(eq(SUB_ID),
+                eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                 eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT), eq(0),
                 eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState(eq(false));
@@ -201,6 +202,7 @@
 
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0),
                         eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         verify(mMockSatelliteModemInterface, times(1))
@@ -225,9 +227,11 @@
         moveTimeForward(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
         processAllMessages();
         mInOrder.verify(mMockDatagramController).updateReceiveStatus(eq(SUB_ID),
+                eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                 eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED), eq(0),
                 eq(SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE));
         mInOrder.verify(mMockDatagramController).updateReceiveStatus(eq(SUB_ID),
+                eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                 eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
                 eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         verifyZeroInteractions(mMockSatelliteModemInterface);
@@ -266,10 +270,12 @@
 
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING), eq(0),
                         eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
                         eq(0), eq(SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR));
 
@@ -289,10 +295,12 @@
 
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE),
                         eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
                         eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
 
@@ -312,10 +320,12 @@
 
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS),
                         eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
                         eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         assertTrue(testSatelliteDatagramCallback.waitForOnSatelliteDatagramReceived());
@@ -339,6 +349,7 @@
 
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS),
                         eq(10), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         verify(mMockSessionMetricsStats, times(1)).addCountOfSuccessfulIncomingDatagram();
@@ -356,16 +367,19 @@
         verify(mMockDatagramController, times(1)).popDemoModeDatagram();
         verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING),
                         anyInt(),
                         eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
                         anyInt(),
                         eq(SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE));
         verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
                         anyInt(),
                         eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
@@ -387,17 +401,20 @@
         verify(mMockDatagramController, never()).popDemoModeDatagram();
         verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING),
                         anyInt(),
                         eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         processAllFutureMessages();
         verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
                         anyInt(),
                         eq(SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE));
         verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
                         anyInt(),
                         eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
@@ -449,10 +466,12 @@
 
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(anyInt(),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED),
                         eq(10), eq(SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED));
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(anyInt(),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
                         eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
     }
@@ -468,6 +487,7 @@
 
         mInOrder.verify(mMockDatagramController)
                 .updateReceiveStatus(anyInt(),
+                        eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
                         eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java
index 873078e..354b20f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java
@@ -80,7 +80,7 @@
         doReturn(Arrays.asList(SATELLITE_PLMN_ARRAY))
                 .when(mMockSatelliteController).getSatellitePlmnsForCarrier(anyInt());
         doReturn(mSatelliteSupportedServiceList).when(mMockSatelliteController)
-                .getSupportedSatelliteServices(SUB_ID, SATELLITE_PLMN);
+                .getSupportedSatelliteServicesForPlmn(SUB_ID, SATELLITE_PLMN);
     }
 
     @After
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 db46a00..d142386 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
@@ -16,6 +16,14 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN;
+import static android.hardware.devicestate.feature.flags.Flags.FLAG_DEVICE_STATE_PROPERTY_MIGRATION;
+import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC;
+import static android.telephony.CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_MANUAL;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT;
 import static android.telephony.CarrierConfigManager.KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT;
@@ -23,14 +31,20 @@
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ESOS_SUPPORTED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_NIDD_APN_NAME_STRING;
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL;
+import static android.telephony.CarrierConfigManager.SATELLITE_DATA_SUPPORT_ALL;
+import static android.telephony.CarrierConfigManager.SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED;
 import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_DATA;
+import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_SMS;
+import static android.telephony.NetworkRegistrationInfo.SERVICE_TYPE_VOICE;
 import static android.telephony.SubscriptionManager.SATELLITE_ENTITLEMENT_STATUS;
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_GOOD;
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_GREAT;
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE;
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_POOR;
 import static android.telephony.satellite.SatelliteManager.KEY_DEMO_MODE_ENABLED;
+import static android.telephony.satellite.SatelliteManager.KEY_DEPROVISION_SATELLITE_TOKENS;
 import static android.telephony.satellite.SatelliteManager.KEY_EMERGENCY_MODE_ENABLED;
 import static android.telephony.satellite.SatelliteManager.KEY_NTN_SIGNAL_STRENGTH;
 import static android.telephony.satellite.SatelliteManager.KEY_PROVISION_SATELLITE_TOKENS;
@@ -41,6 +55,7 @@
 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_NEXT_VISIBILITY;
 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_PROVISIONED;
 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_SUPPORTED;
+import static android.telephony.satellite.SatelliteManager.METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT;
 import static android.telephony.satellite.SatelliteManager.NT_RADIO_TECHNOLOGY_EMTC_NTN;
 import static android.telephony.satellite.SatelliteManager.NT_RADIO_TECHNOLOGY_NB_IOT_NTN;
 import static android.telephony.satellite.SatelliteManager.NT_RADIO_TECHNOLOGY_NR_NTN;
@@ -71,16 +86,20 @@
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
 import static com.android.internal.telephony.satellite.SatelliteController.DEFAULT_CARRIER_EMERGENCY_CALL_WAIT_FOR_CONNECTION_TIMEOUT_MILLIS;
+import static com.android.internal.telephony.satellite.SatelliteController.SATELLITE_DATA_PLAN_METERED;
+import static com.android.internal.telephony.satellite.SatelliteController.SATELLITE_DATA_PLAN_UNMETERED;
 import static com.android.internal.telephony.satellite.SatelliteController.SATELLITE_MODE_ENABLED_FALSE;
 import static com.android.internal.telephony.satellite.SatelliteController.SATELLITE_MODE_ENABLED_TRUE;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -89,6 +108,7 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.anyVararg;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
@@ -104,10 +124,16 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.hardware.devicestate.DeviceState;
+import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -119,14 +145,20 @@
 import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellSignalStrength;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
+import android.telephony.satellite.EarfcnRange;
 import android.telephony.satellite.INtnSignalStrengthCallback;
 import android.telephony.satellite.ISatelliteCapabilitiesCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
@@ -134,17 +166,22 @@
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteSupportedStateCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
+import android.telephony.satellite.ISelectedNbIotSatelliteSubscriptionCallback;
 import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteInfo;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.SatelliteManager.SatelliteException;
 import android.telephony.satellite.SatelliteModemEnableRequestAttributes;
+import android.telephony.satellite.SatellitePosition;
 import android.telephony.satellite.SatelliteSubscriberInfo;
 import android.telephony.satellite.SatelliteSubscriberProvisionStatus;
 import android.telephony.satellite.SatelliteSubscriptionInfo;
+import android.telephony.satellite.SystemSelectionSpecifier;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.util.IntArray;
 import android.util.Pair;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -166,6 +203,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -181,9 +219,12 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -206,6 +247,7 @@
     private static final int TEST_WAIT_FOR_CELLULAR_MODEM_OFF_TIMEOUT_MILLIS =
             (int) TimeUnit.SECONDS.toMillis(60);
 
+
     private static final String SATELLITE_PLMN = "00103";
     private List<Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener>>
             mCarrierConfigChangedListenerList = new ArrayList<>();
@@ -215,6 +257,9 @@
     private PersistableBundle mCarrierConfigBundle;
     private ServiceState mServiceState2;
 
+    private SubscriptionInfo testSubscriptionInfo;
+    private SubscriptionInfo testSubscriptionInfo2;
+
     @Mock private SatelliteController mMockSatelliteController;
     @Mock private DatagramController mMockDatagramController;
     @Mock private SatelliteModemInterface mMockSatelliteModemInterface;
@@ -237,6 +282,7 @@
     @Mock private Resources mResources;
     @Mock private SubscriptionManager mSubscriptionManager;
     @Mock private SubscriptionInfo mSubscriptionInfo;
+    @Mock private PackageManager mMockPManager;
 
     private Semaphore mIIntegerConsumerSemaphore = new Semaphore(0);
     private IIntegerConsumer mIIntegerConsumer = new IIntegerConsumer.Stub() {
@@ -513,6 +559,26 @@
         }
     };
 
+    private int mQueriedSystemSelectionChannelUpdatedResultCode = SATELLITE_RESULT_SUCCESS;
+    private Semaphore mSystemSelectionChannelUpdatedSemaphore = new Semaphore(0);
+    private ResultReceiver mSystemSelectionChannelUpdatedReceiver = new ResultReceiver(null) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mQueriedSystemSelectionChannelUpdatedResultCode = resultCode;
+            try {
+                mSystemSelectionChannelUpdatedSemaphore.release();
+            } catch (Exception ex) {
+                fail("mSystemSelectionChannelUpdatedReceiver: Got exception in releasing "
+                        + "semaphore, ex="
+                        + ex);
+            }
+        }
+    };
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
@@ -548,9 +614,11 @@
         when(mPhone.getServiceState()).thenReturn(mServiceState);
         when(mPhone.getSubId()).thenReturn(SUB_ID);
         when(mPhone.getPhoneId()).thenReturn(0);
+        when(mPhone.getSignalStrengthController()).thenReturn(mSignalStrengthController);
         when(mPhone2.getServiceState()).thenReturn(mServiceState2);
         when(mPhone2.getSubId()).thenReturn(SUB_ID1);
         when(mPhone2.getPhoneId()).thenReturn(1);
+        when(mPhone2.getSignalStrengthController()).thenReturn(mSignalStrengthController);
 
         mContextFixture.putStringArrayResource(
                 R.array.config_satellite_providers,
@@ -561,6 +629,9 @@
         mContextFixture.putIntResource(
                 R.integer.config_satellite_wait_for_cellular_modem_off_timeout_millis,
                 TEST_WAIT_FOR_CELLULAR_MODEM_OFF_TIMEOUT_MILLIS);
+        mContextFixture.putIntArrayResource(
+                R.array.config_foldedDeviceStates,
+                new int[0]);
         doReturn(ACTIVE_SUB_IDS).when(mMockSubscriptionManagerService).getActiveSubIdList(true);
 
         mCarrierConfigBundle = mContextFixture.getCarrierConfigBundle();
@@ -608,6 +679,8 @@
                 .when(mMockSessionMetricsStats).setIsDemoMode(anyBoolean());
         doReturn(mMockSessionMetricsStats)
                 .when(mMockSessionMetricsStats).setCarrierId(anyInt());
+        doReturn(mMockSessionMetricsStats)
+                .when(mMockSessionMetricsStats).setIsNtnOnlyCarrier(anyBoolean());
         doNothing().when(mMockSessionMetricsStats).reportSessionMetrics();
 
         doReturn(mMockProvisionMetricsStats).when(mMockProvisionMetricsStats)
@@ -616,10 +689,12 @@
                 .setIsProvisionRequest(anyBoolean());
         doReturn(mMockProvisionMetricsStats).when(mMockProvisionMetricsStats)
                 .setCarrierId(anyInt());
+        doReturn(mMockProvisionMetricsStats).when(mMockProvisionMetricsStats)
+                .setIsNtnOnlyCarrier(anyBoolean());
         doNothing().when(mMockProvisionMetricsStats).reportProvisionMetrics();
         doNothing().when(mMockControllerMetricsStats).reportDeprovisionCount(anyInt());
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
-        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(false);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
         doReturn(mSST).when(mPhone).getServiceStateTracker();
         doReturn(mSST).when(mPhone2).getServiceStateTracker();
         doReturn(mServiceState).when(mSST).getServiceState();
@@ -922,6 +997,7 @@
     @Test
     public void testRequestSatelliteEnabled() {
         when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mFeatureFlags.satelliteStateChangeListener()).thenReturn(true);
         mIsSatelliteEnabledSemaphore.drainPermits();
 
         // Fail to enable satellite when SatelliteController is not fully loaded yet.
@@ -978,6 +1054,7 @@
         doReturn(false).when(mTelecomManager).isInEmergencyCall();
 
         // Successfully enable satellite
+        reset(mTelephonyRegistryManager);
         mIIntegerConsumerResults.clear();
         mIIntegerConsumerSemaphore.drainPermits();
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
@@ -999,12 +1076,14 @@
         verify(mMockDatagramController, times(2)).setDemoMode(eq(false));
         verify(mMockControllerMetricsStats, times(1)).onSatelliteEnabled();
         verify(mMockControllerMetricsStats, times(1)).reportServiceEnablementSuccessCount();
+        verify(mTelephonyRegistryManager).notifySatelliteStateChanged(eq(true));
 
         // Successfully disable satellite when radio is turned off.
+        reset(mTelephonyRegistryManager);
         clearInvocations(mMockSatelliteSessionController);
         clearInvocations(mMockDatagramController);
         mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
-        when(mMockSatelliteSessionController.isInDisablingState()).thenReturn(true);
+        mSatelliteControllerUT.isSatelliteBeingDisabled = true;
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
         mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
         setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
@@ -1023,7 +1102,8 @@
         verify(mMockSatelliteSessionController, times(2)).setDemoMode(eq(false));
         verify(mMockDatagramController, times(2)).setDemoMode(eq(false));
         verify(mMockControllerMetricsStats, times(1)).onSatelliteDisabled();
-        when(mMockSatelliteSessionController.isInDisablingState()).thenReturn(false);
+        mSatelliteControllerUT.isSatelliteBeingDisabled = false;
+        verify(mTelephonyRegistryManager, atLeastOnce()).notifySatelliteStateChanged(eq(false));
 
         // Fail to enable satellite when radio is off.
         mIIntegerConsumerResults.clear();
@@ -1059,6 +1139,7 @@
         verify(mMockControllerMetricsStats, times(1)).reportServiceEnablementFailCount();
 
         // Successfully enable satellite when radio is on.
+        reset(mTelephonyRegistryManager);
         mIIntegerConsumerResults.clear();
         mIIntegerConsumerSemaphore.drainPermits();
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
@@ -1077,6 +1158,7 @@
         verify(mMockDatagramController, times(3)).setDemoMode(eq(false));
         verify(mMockControllerMetricsStats, times(2)).onSatelliteEnabled();
         verify(mMockControllerMetricsStats, times(2)).reportServiceEnablementSuccessCount();
+        verify(mTelephonyRegistryManager).notifySatelliteStateChanged(eq(true));
 
         // Successfully enable satellite when it is already enabled.
         mIIntegerConsumerResults.clear();
@@ -1097,6 +1179,7 @@
         verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
 
         // Successfully disable satellite.
+        reset(mTelephonyRegistryManager);
         mIIntegerConsumerResults.clear();
         mIIntegerConsumerSemaphore.drainPermits();
         setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
@@ -1105,6 +1188,7 @@
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
         verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+        verify(mTelephonyRegistryManager, atLeastOnce()).notifySatelliteStateChanged(eq(false));
 
         // Disable satellite when satellite is already disabled.
         mIIntegerConsumerResults.clear();
@@ -1171,7 +1255,7 @@
         mIIntegerConsumerResults.clear();
         mIIntegerConsumerSemaphore.drainPermits();
         mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
-        when(mMockSatelliteSessionController.isInDisablingState()).thenReturn(true);
+        mSatelliteControllerUT.isSatelliteBeingDisabled = true;
         mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -1180,7 +1264,7 @@
         mIIntegerConsumerResults.clear();
         mIIntegerConsumerSemaphore.drainPermits();
         resetSatelliteControllerUTToOffAndProvisionedState();
-        when(mMockSatelliteSessionController.isInDisablingState()).thenReturn(false);
+        mSatelliteControllerUT.isSatelliteBeingDisabled = false;
 
         /**
          * Make areAllRadiosDisabled return false and move mWaitingForRadioDisabled to true, which
@@ -1547,8 +1631,8 @@
     @Test
     public void testIsSatelliteEnabled() {
         logd("testIsSatelliteEnabled: starting");
+        assertFalse(mSatelliteControllerUT.isSatelliteEnabledOrBeingEnabled());
         setUpResponseForRequestIsSatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
-        assertFalse(mSatelliteControllerUT.isSatelliteEnabled());
         mIsSatelliteEnabledSemaphore.drainPermits();
         mSatelliteControllerUT.requestIsSatelliteEnabled(mIsSatelliteEnabledReceiver);
         processAllMessages();
@@ -1564,7 +1648,8 @@
         mSatelliteControllerUT.requestIsSatelliteEnabled(mIsSatelliteEnabledReceiver);
         processAllMessages();
         assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedIsSatelliteEnabledResultCode);
-        assertEquals(mSatelliteControllerUT.isSatelliteEnabled(), mQueriedIsSatelliteEnabled);
+        assertEquals(mSatelliteControllerUT.isSatelliteEnabledOrBeingEnabled(),
+                mQueriedIsSatelliteEnabled);
     }
 
     @Test
@@ -1602,6 +1687,11 @@
             public void onRegistrationFailure(int causeCode) {
                 logd("onRegistrationFailure: causeCode=" + causeCode);
             }
+
+            @Override
+            public void onTerrestrialNetworkAvailableChanged(boolean isAvailable) {
+                logd("onTerrestrialNetworkAvailableChanged: isAvailable=" + isAvailable);
+            }
         };
         int errorCode = mSatelliteControllerUT.registerForSatelliteModemStateChanged(callback);
         assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, errorCode);
@@ -1633,6 +1723,11 @@
             public void onRegistrationFailure(int causeCode) {
                 logd("onRegistrationFailure: causeCode=" + causeCode);
             }
+
+            @Override
+            public void onTerrestrialNetworkAvailableChanged(boolean isAvailable) {
+                logd("onTerrestrialNetworkAvailableChanged: isAvailable=" + isAvailable);
+            }
         };
         mSatelliteControllerUT.unregisterForModemStateChanged(callback);
         verify(mMockSatelliteSessionController, never())
@@ -1646,6 +1741,7 @@
 
     @Test
     public void testRegisterForSatelliteProvisionStateChanged() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
         Semaphore semaphore = new Semaphore(0);
         ISatelliteProvisionStateCallback callback =
                 new ISatelliteProvisionStateCallback.Stub() {
@@ -1667,12 +1763,7 @@
                     }
                 };
         int errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(callback);
-        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, errorCode);
-
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(callback);
-        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, errorCode);
+        assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
 
         resetSatelliteControllerUT();
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
@@ -1683,6 +1774,14 @@
                 semaphore, 1, "testRegisterForSatelliteProvisionStateChanged"));
         assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
 
+        try {
+            setSatelliteSubscriberTesting(true);
+        } catch (Exception ex) {
+            fail("provisionSatelliteService.setSatelliteSubscriberTesting: ex=" + ex);
+        }
+        doReturn(true).when(mMockSubscriptionManagerService).isSatelliteProvisionedForNonIpDatagram(
+                anyInt());
+
         String mText = "This is test provision data.";
         byte[] testProvisionData = mText.getBytes();
         CancellationSignal cancellationSignal = new CancellationSignal();
@@ -1694,8 +1793,8 @@
         processAllMessages();
         assertTrue(waitForForEvents(
                 semaphore, 1, "testRegisterForSatelliteProvisionStateChanged"));
-
         mSatelliteControllerUT.unregisterForSatelliteProvisionStateChanged(callback);
+        semaphore.drainPermits();
         cancelRemote = mSatelliteControllerUT.provisionSatelliteService(
                 TEST_SATELLITE_TOKEN,
                 testProvisionData, mIIntegerConsumer);
@@ -1715,11 +1814,11 @@
                         logd("onSatelliteDatagramReceived");
                     }
                 };
-        when(mMockDatagramController.registerForSatelliteDatagram(eq(SUB_ID), eq(callback)))
+        when(mMockDatagramController.registerForSatelliteDatagram(anyInt(), eq(callback)))
                 .thenReturn(SATELLITE_RESULT_SUCCESS);
         int errorCode = mSatelliteControllerUT.registerForIncomingDatagram(callback);
         assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
-        verify(mMockDatagramController).registerForSatelliteDatagram(eq(SUB_ID), eq(callback));
+        verify(mMockDatagramController).registerForSatelliteDatagram(anyInt(), eq(callback));
     }
 
     @Test
@@ -1734,9 +1833,9 @@
                     }
                 };
         doNothing().when(mMockDatagramController)
-                .unregisterForSatelliteDatagram(eq(SUB_ID), eq(callback));
+                .unregisterForSatelliteDatagram(anyInt(), eq(callback));
         mSatelliteControllerUT.unregisterForIncomingDatagram(callback);
-        verify(mMockDatagramController).unregisterForSatelliteDatagram(eq(SUB_ID), eq(callback));
+        verify(mMockDatagramController).unregisterForSatelliteDatagram(anyInt(), eq(callback));
     }
 
     @Test
@@ -1829,6 +1928,8 @@
 
     @Test
     public void testProvisionSatelliteService() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(false);
+
         String mText = "This is test provision data.";
         byte[] testProvisionData = mText.getBytes();
         CancellationSignal cancellationSignal = new CancellationSignal();
@@ -1913,6 +2014,7 @@
 
     @Test
     public void testDeprovisionSatelliteService() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(false);
         mIIntegerConsumerSemaphore.drainPermits();
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
@@ -1984,7 +2086,7 @@
                 SUB_ID);
         assertEquals(EMPTY_STRING_ARRAY.length, satellitePlmnList.size());
         List<Integer> supportedSatelliteServices =
-                mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00101");
+                mSatelliteControllerUT.getSupportedSatelliteServicesForPlmn(SUB_ID, "00101");
         assertTrue(supportedSatelliteServices.isEmpty());
 
         String[] satelliteProviderStrArray = {"00101", "00102"};
@@ -2011,7 +2113,7 @@
         satellitePlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
         assertTrue(satellitePlmnList.isEmpty());
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00101");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID, "00101");
         assertTrue(supportedSatelliteServices.isEmpty());
 
         // Add entitlement provided PLMNs.
@@ -2027,16 +2129,16 @@
         processAllMessages();
 
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00102");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID, "00102");
         assertTrue(supportedSatelliteServices.isEmpty());
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00103");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID, "00103");
         assertTrue(supportedSatelliteServices.isEmpty());
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00104");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID, "00104");
         assertTrue(supportedSatelliteServices.isEmpty());
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00105");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID, "00105");
         assertTrue(supportedSatelliteServices.isEmpty());
 
         // Trigger carrier config changed with carrierEnabledSatelliteFlag enabled
@@ -2055,27 +2157,27 @@
         assertTrue(Arrays.equals(
                 expectedSupportedSatellitePlmns, satellitePlmnList.stream().toArray()));
         supportedSatelliteServices =
-                mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00102");
+                mSatelliteControllerUT.getSupportedSatelliteServicesForPlmn(SUB_ID, "00102");
         // "00101" should return carrier config assigned value, though it is in allowed list.
         assertTrue(Arrays.equals(expectedSupportedServices2,
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
         supportedSatelliteServices =
-                mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00103");
+                mSatelliteControllerUT.getSupportedSatelliteServicesForPlmn(SUB_ID, "00103");
         assertTrue(Arrays.equals(expectedSupportedServices3,
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
         // "00104", and "00105" should return default supported service.
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00104");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID, "00104");
         assertTrue(Arrays.equals(defaultSupportedServices,
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00105");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID, "00105");
         assertTrue(Arrays.equals(defaultSupportedServices,
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
@@ -2096,33 +2198,33 @@
         assertTrue(satellitePlmnList.isEmpty());
         // "00102" and "00103" should return default supported service for SUB_ID.
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00102");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID, "00102");
         assertTrue(Arrays.equals(defaultSupportedServices,
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00103");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID, "00103");
         assertTrue(Arrays.equals(defaultSupportedServices,
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
         // "00104", and "00105" should return default supported service for SUB_ID.
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00104");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID, "00104");
         assertTrue(Arrays.equals(defaultSupportedServices,
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00105");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID, "00105");
         assertTrue(Arrays.equals(defaultSupportedServices,
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
 
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00102");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID1, "00102");
         assertNotNull(supportedSatelliteServices);
         assertTrue(Arrays.equals(expectedSupportedServices2,
                 supportedSatelliteServices.stream()
@@ -2130,20 +2232,20 @@
                         .toArray()));
 
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00103");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID1, "00103");
         assertTrue(Arrays.equals(expectedSupportedServices3,
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
         /* "00104", and "00105" should return default supported service. */
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00104");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID1, "00104");
         assertTrue(Arrays.equals(defaultSupportedServices,
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
         supportedSatelliteServices =
-                testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00105");
+                testSatelliteController.getSupportedSatelliteServicesForPlmn(SUB_ID1, "00105");
         assertTrue(Arrays.equals(defaultSupportedServices,
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
@@ -2491,6 +2593,7 @@
         assertTrue(
                 mQueriedSatelliteCapabilities.getSupportedRadioTechnologies().contains(
                         satelliteController.getSupportedNtnRadioTechnology()));
+        assertEquals(mQueriedSatelliteCapabilities.getMaxBytesPerOutgoingDatagram(), 255);
         assertTrue(satelliteController.isSatelliteAttachRequired());
 
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
@@ -2517,14 +2620,14 @@
 
         clearInvocations(mMockSatelliteSessionController);
         clearInvocations(mMockDatagramController);
-        when(mMockSatelliteSessionController.isInDisablingState()).thenReturn(true);
+        mSatelliteControllerUT.isSatelliteBeingDisabled = true;
         sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_NOT_CONNECTED, null);
         processAllMessages();
         verify(mMockSatelliteSessionController, times(1)).onSatelliteModemStateChanged(
                 SATELLITE_MODEM_STATE_NOT_CONNECTED);
 
         clearInvocations(mMockSatelliteSessionController);
-        when(mMockSatelliteSessionController.isInDisablingState()).thenReturn(false);
+        mSatelliteControllerUT.isSatelliteBeingDisabled = false;
         sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_NOT_CONNECTED, null);
         processAllMessages();
         verify(mMockSatelliteSessionController, never()).onSatelliteModemStateChanged(
@@ -2534,7 +2637,6 @@
     @Test
     public void testRequestNtnSignalStrengthWithFeatureFlagEnabled() {
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
-
         resetSatelliteControllerUT();
 
         mRequestNtnSignalStrengthSemaphore.drainPermits();
@@ -2878,7 +2980,8 @@
     @Test
     public void testCarrierEnabledSatelliteConnectionHysteresisTime() throws Exception {
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
-        assertFalse(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+        assertFalse(mSatelliteControllerUT
+                        .isSatelliteConnectedViaCarrierWithinHysteresisTime().first);
 
         when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
         when(mServiceState2.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
@@ -2898,7 +3001,8 @@
         doReturn(cellSignalStrengthList).when(mSignalStrength).getCellSignalStrengths();
         processAllMessages();
         mSatelliteControllerUT.elapsedRealtime = 0;
-        assertFalse(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+        assertFalse(mSatelliteControllerUT
+                        .isSatelliteConnectedViaCarrierWithinHysteresisTime().first);
         assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone));
         assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone2));
 
@@ -2906,7 +3010,8 @@
         when(mServiceState2.isUsingNonTerrestrialNetwork()).thenReturn(false);
         sendServiceStateChangedEvent();
         processAllMessages();
-        assertFalse(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+        assertFalse(mSatelliteControllerUT
+                        .isSatelliteConnectedViaCarrierWithinHysteresisTime().first);
         assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone));
         assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone2));
         verify(mPhone, times(1)).notifyCarrierRoamingNtnModeChanged(eq(false));
@@ -2921,7 +3026,8 @@
         // 2 minutes later and hysteresis timeout is 1 minute
         mSatelliteControllerUT.elapsedRealtime = 2 * 60 * 1000;
         // But Phone2 is connected to NTN right now
-        assertTrue(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+        assertTrue(mSatelliteControllerUT
+                       .isSatelliteConnectedViaCarrierWithinHysteresisTime().first);
         assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone));
         assertTrue(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone2));
         verify(mPhone, times(0)).notifyCarrierRoamingNtnModeChanged(eq(false));
@@ -2934,7 +3040,8 @@
         sendServiceStateChangedEvent();
         processAllMessages();
         // Current time (2) - last disconnected time (2) < hysteresis timeout (1)
-        assertTrue(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+        assertTrue(mSatelliteControllerUT
+                       .isSatelliteConnectedViaCarrierWithinHysteresisTime().first);
         assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone));
         assertTrue(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone2));
         verify(mPhone, times(0)).notifyCarrierRoamingNtnModeChanged(eq(false));
@@ -2946,7 +3053,8 @@
         mSatelliteControllerUT.elapsedRealtime = 4 * 60 * 1000;
         moveTimeForward(2 * 60 * 1000);
         processAllMessages();
-        assertFalse(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+        assertFalse(mSatelliteControllerUT
+                        .isSatelliteConnectedViaCarrierWithinHysteresisTime().first);
         assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone));
         assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone2));
         verify(mPhone, times(0)).notifyCarrierRoamingNtnModeChanged(eq(false));
@@ -3077,7 +3185,8 @@
         // Verify call the requestSetSatelliteEnabledForCarrier to enable the satellite when
         // satellite service is enabled by entitlement server.
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, true, new ArrayList<>(),
-                new ArrayList<>(), mIIntegerConsumer);
+                new ArrayList<>(), new HashMap<>(), new HashMap<>(), new HashMap<>(),
+                new HashMap<>(), mIIntegerConsumer);
         processAllMessages();
 
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -3097,7 +3206,8 @@
                 .when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
         setUpResponseForRequestSetSatelliteEnabledForCarrier(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false, new ArrayList<>(),
-                new ArrayList<>(), mIIntegerConsumer);
+                new ArrayList<>(), new HashMap<>(), new HashMap<>(), new HashMap<>(),
+                new HashMap<>(), mIIntegerConsumer);
         processAllMessages();
 
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -3128,7 +3238,8 @@
         List<String> entitlementPlmnList = new ArrayList<>();
         List<String> barredPlmnList = new ArrayList<>();
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
-                entitlementPlmnList, barredPlmnList, mIIntegerConsumer);
+                entitlementPlmnList, barredPlmnList, new HashMap<>(), new HashMap<>(),
+                new HashMap<>(), new HashMap<>(), mIIntegerConsumer);
         verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(
                 any(SatelliteModemEnableRequestAttributes.class), any(Message.class));
 
@@ -3186,7 +3297,8 @@
         reset(mMockSatelliteModemInterface);
         entitlementPlmnList = Arrays.stream(new String[]{"00101", "00102", ""}).toList();
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
-                entitlementPlmnList, barredPlmnList, mIIntegerConsumer);
+                entitlementPlmnList, barredPlmnList, new HashMap<>(), new HashMap<>(),
+                new HashMap<>(), new HashMap<>(), mIIntegerConsumer);
         verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(
                 any(SatelliteModemEnableRequestAttributes.class), any(Message.class));
 
@@ -3194,7 +3306,8 @@
         reset(mMockSatelliteModemInterface);
         entitlementPlmnList = Arrays.stream(new String[]{"00101", "00102", "123456789"}).toList();
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
-                entitlementPlmnList, barredPlmnList, mIIntegerConsumer);
+                entitlementPlmnList, barredPlmnList, new HashMap<>(), new HashMap<>(),
+                new HashMap<>(), new HashMap<>(), mIIntegerConsumer);
         verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(
                 any(SatelliteModemEnableRequestAttributes.class), any(Message.class));
 
@@ -3202,7 +3315,8 @@
         reset(mMockSatelliteModemInterface);
         entitlementPlmnList = Arrays.stream(new String[]{"00101", "00102", "12"}).toList();
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
-                entitlementPlmnList, barredPlmnList, mIIntegerConsumer);
+                entitlementPlmnList, barredPlmnList, new HashMap<>(), new HashMap<>(),
+                new HashMap<>(), new HashMap<>(), mIIntegerConsumer);
         verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(
                 any(SatelliteModemEnableRequestAttributes.class), any(Message.class));
 
@@ -3210,7 +3324,8 @@
         reset(mMockSatelliteModemInterface);
         entitlementPlmnList = Arrays.stream(new String[]{"00101", "00102", "1234"}).toList();
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
-                entitlementPlmnList, barredPlmnList, mIIntegerConsumer);
+                entitlementPlmnList, barredPlmnList, new HashMap<>(), new HashMap<>(),
+                new HashMap<>(), new HashMap<>(), mIIntegerConsumer);
         verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(
                 any(SatelliteModemEnableRequestAttributes.class), any(Message.class));
     }
@@ -3219,7 +3334,8 @@
             List<String> mergedPlmnList, List<String> overlayConfigPlmnList,
             List<String> barredPlmnList) {
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
-                entitlementPlmnList, barredPlmnList, mIIntegerConsumer);
+                entitlementPlmnList, barredPlmnList, new HashMap<>(), new HashMap<>(),
+                new HashMap<>(), new HashMap<>(), mIIntegerConsumer);
 
         List<String> plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(
                 SUB_ID);
@@ -3267,7 +3383,8 @@
         setConfigData(new ArrayList<>());
         setCarrierConfigDataPlmnList(new ArrayList<>());
         invokeCarrierConfigChanged();
-        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "31016");
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServicesForPlmn(
+                SUB_ID, "31016");
         assertEquals(new ArrayList<>(), servicesPerPlmn);
 
         // Verify whether the carrier config plmn list is returned with conditions below
@@ -3276,13 +3393,16 @@
         setConfigData(new ArrayList<>());
         setCarrierConfigDataPlmnList(Arrays.asList("00101", "00102", "00104"));
         invokeCarrierConfigChanged();
-        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00101");
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServicesForPlmn(
+                SUB_ID, "00101");
         assertEquals(Arrays.asList(2).stream().sorted().toList(),
                 servicesPerPlmn.stream().sorted().toList());
-        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00102");
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServicesForPlmn(
+                SUB_ID, "00102");
         assertEquals(Arrays.asList(1, 3).stream().sorted().toList(),
                 servicesPerPlmn.stream().sorted().toList());
-        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00104");
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServicesForPlmn(
+                SUB_ID, "00104");
         assertEquals(Arrays.asList(2).stream().sorted().toList(),
                 servicesPerPlmn.stream().sorted().toList());
 
@@ -3292,15 +3412,19 @@
         setConfigData(Arrays.asList("00101", "00102", "31024"));
         setCarrierConfigDataPlmnList(Arrays.asList("00101", "00102", "00104"));
         invokeCarrierConfigChanged();
-        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00101");
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServicesForPlmn(
+                SUB_ID, "00101");
         assertEquals(Arrays.asList(1).stream().sorted().toList(),
                 servicesPerPlmn.stream().sorted().toList());
-        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00102");
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServicesForPlmn(
+                SUB_ID, "00102");
         assertEquals(Arrays.asList(3).stream().sorted().toList(),
                 servicesPerPlmn.stream().sorted().toList());
-        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "00104");
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServicesForPlmn(
+                SUB_ID, "00104");
         assertEquals(new ArrayList<>(), servicesPerPlmn.stream().sorted().toList());
-        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServices(SUB_ID, "31024");
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServicesForPlmn(
+                SUB_ID, "31024");
         assertEquals(Arrays.asList(5).stream().sorted().toList(),
                 servicesPerPlmn.stream().sorted().toList());
     }
@@ -3433,21 +3557,24 @@
 
         // Change SUB_ID's EntitlementStatus to true
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, true, new ArrayList<>(),
-                new ArrayList<>(), mIIntegerConsumer);
+                new ArrayList<>(), new HashMap<>(), new HashMap<>(),
+                new HashMap<>(), new HashMap<>(), mIIntegerConsumer);
 
         assertEquals(true, satelliteEnabledPerCarrier.get(SUB_ID));
         assertEquals(false, satelliteEnabledPerCarrier.get(SUB_ID1));
 
         // Change SUB_ID1's EntitlementStatus to true
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID1, true, new ArrayList<>(),
-                new ArrayList<>(), mIIntegerConsumer);
+                new ArrayList<>(), new HashMap<>(), new HashMap<>(), new HashMap<>(),
+                new HashMap<>(), mIIntegerConsumer);
 
         assertEquals(true, satelliteEnabledPerCarrier.get(SUB_ID));
         assertEquals(true, satelliteEnabledPerCarrier.get(SUB_ID1));
 
         // Change SUB_ID's EntitlementStatus to false
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false, new ArrayList<>(),
-                new ArrayList<>(), mIIntegerConsumer);
+                new ArrayList<>(), new HashMap<>(), new HashMap<>(), new HashMap<>(),
+                new HashMap<>(), mIIntegerConsumer);
 
         assertEquals(false, satelliteEnabledPerCarrier.get(SUB_ID));
         assertEquals(true, satelliteEnabledPerCarrier.get(SUB_ID1));
@@ -3579,10 +3706,18 @@
 
     @Test
     public void testHandleEventServiceStateChanged() {
+        mContextFixture.putBooleanResource(
+            R.bool.config_satellite_should_notify_availability, true);
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mCarrierConfigBundle.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT,
+                CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC);
+        invokeCarrierConfigChanged();
+
         // Do nothing when the satellite is not connected
         doReturn(false).when(mServiceState).isUsingNonTerrestrialNetwork();
         sendServiceStateChangedEvent();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         processAllMessages();
         assertFalse(mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false));
         verify(mMockNotificationManager, never()).notifyAsUser(anyString(), anyInt(), any(), any());
@@ -3908,6 +4043,96 @@
     }
 
     @Test
+    public void testRegisterForSelectedNbIotSatelliteSubscriptionChanged_WithFeatureFlagEnabled() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+
+        Semaphore semaphore = new Semaphore(0);
+        final int[] selectedSubIds = new int[1];
+        ISelectedNbIotSatelliteSubscriptionCallback callback =
+                new ISelectedNbIotSatelliteSubscriptionCallback.Stub() {
+                    @Override
+                    public void onSelectedNbIotSatelliteSubscriptionChanged(int selectedSubId) {
+                        logd("onSelectedNbIotSatelliteSubscriptionChanged: selectedSubId="
+                                + selectedSubId);
+                        try {
+                            selectedSubIds[0] = selectedSubId;
+                            semaphore.release();
+                        } catch (Exception ex) {
+                            loge("onSelectedNbIotSatelliteSubscriptionChanged: Got exception in "
+                                    + "releasing semaphore, ex=" + ex);
+                        }
+                    }
+                };
+
+        int errorCode = mSatelliteControllerUT.registerForSelectedNbIotSatelliteSubscriptionChanged(
+                callback);
+        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, errorCode);
+
+        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+        errorCode = mSatelliteControllerUT.registerForSelectedNbIotSatelliteSubscriptionChanged(
+                callback);
+        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, errorCode);
+
+        // Register the callback and verify that the event is reported.
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteProvisioned(true,SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        errorCode = mSatelliteControllerUT.registerForSelectedNbIotSatelliteSubscriptionChanged(
+                callback);
+        assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
+        int expectedSubId = 1;
+        sendSelectedNbIotSatelliteSubscriptionChangedEvent(expectedSubId, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSelectedNbIotSatelliteSubscriptionChanged"));
+        assertEquals(expectedSubId, selectedSubIds[0]);
+
+        // Unregister the callback and verify that the event is not reported.
+        mSatelliteControllerUT.unregisterForSelectedNbIotSatelliteSubscriptionChanged(callback);
+        sendSelectedNbIotSatelliteSubscriptionChangedEvent(2, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 0, "testRegisterForSelectedNbIotSatelliteSubscriptionChanged"));
+    }
+
+    @Test
+    public void testRegisterForSelectedNbIotSatelliteSubscriptionChanged_WithFeatureFlagDisabled() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(false);
+
+        Semaphore semaphore = new Semaphore(0);
+        final int[] selectedSubIds = new int[1];
+        ISelectedNbIotSatelliteSubscriptionCallback callback =
+                new ISelectedNbIotSatelliteSubscriptionCallback.Stub() {
+                    @Override
+                    public void onSelectedNbIotSatelliteSubscriptionChanged(int selectedSubId) {
+                        logd("onSelectedNbIotSatelliteSubscriptionChanged: selectedSubId="
+                                + selectedSubId);
+                        try {
+                            selectedSubIds[0] = selectedSubId;
+                            semaphore.release();
+                        } catch (Exception ex) {
+                            loge("onSelectedNbIotSatelliteSubscriptionChanged: Got exception in "
+                                    + "releasing semaphore, ex=" + ex);
+                        }
+                    }
+                };
+
+        int errorCode = mSatelliteControllerUT.registerForSelectedNbIotSatelliteSubscriptionChanged(
+                callback);
+        assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, errorCode);
+
+        // Verify that the event is not reported.
+        sendSelectedNbIotSatelliteSubscriptionChangedEvent(1, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 0, "testRegisterForSelectedNbIotSatelliteSubscriptionChanged"));
+
+
+    }
+
+    @Test
     public void testIsSatelliteEmergencyMessagingSupportedViaCarrier() {
         // Carrier-enabled flag is off
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
@@ -4015,8 +4240,10 @@
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
         when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
         when(mServiceState2.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        mSatelliteControllerUT.mIsApplicationSupportsP2P = true;
         mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
         mCarrierConfigBundle.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 1);
+        mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL, true);
         int[] supportedServices2 = {2};
         int[] supportedServices3 = {1, 3};
         PersistableBundle carrierSupportedSatelliteServicesPerProvider = new PersistableBundle();
@@ -4034,21 +4261,57 @@
             );
         }
         mSatelliteControllerUT.setSatellitePhone(1);
+        mSatelliteControllerUT.setSelectedSatelliteSubId(SUB_ID);
+        mSatelliteControllerUT.isSatelliteProvisioned = true;
+        mSatelliteControllerUT.setIsSatelliteAllowedState(true);
         processAllMessages();
 
         assertTrue(mSatelliteControllerUT.isCarrierRoamingNtnEligible(mPhone));
+
+        when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        when(mServiceState2.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        processAllMessages();
+        assertFalse(mSatelliteControllerUT.isCarrierRoamingNtnEligible(mPhone));
+
+        when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        when(mServiceState2.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        mSatelliteControllerUT.overrideCarrierRoamingNtnEligibilityChanged(true, false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isCarrierRoamingNtnEligible(mPhone));
     }
 
     @Test
+    public void testOverrideCarrierRoamingNtNEligibilityChange() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mSatelliteControllerUT.overrideCarrierRoamingNtnEligibilityChanged(true, false);
+        verify(mPhone, times(1)).notifyCarrierRoamingNtnEligibleStateChanged(eq(true));
+        clearInvocations(mPhone);
+
+        mSatelliteControllerUT.overrideCarrierRoamingNtnEligibilityChanged(false, false);
+        verify(mPhone, times(1)).notifyCarrierRoamingNtnEligibleStateChanged(eq(false));
+        clearInvocations(mPhone);
+
+        mSatelliteControllerUT.overrideCarrierRoamingNtnEligibilityChanged(false, true);
+        verify(mPhone, times(0)).notifyCarrierRoamingNtnEligibleStateChanged(eq(true));
+        clearInvocations(mPhone);
+    }
+
+    @Test
     public void testNotifyNtnEligibilityHysteresisTimedOut() {
+        mContextFixture.putBooleanResource(
+            R.bool.config_satellite_should_notify_availability, true);
         when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
         when(mServiceState2.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
         when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        mSatelliteControllerUT.mIsApplicationSupportsP2P = true;
+        mSatelliteControllerUT.setIsSatelliteSupported(true);
         mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
-        mCarrierConfigBundle.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 1);
+        mCarrierConfigBundle.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT,
+            CARRIER_ROAMING_NTN_CONNECT_MANUAL);
         mCarrierConfigBundle.putInt(
                 KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT, 1 * 60);
+        mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL, true);
         int[] supportedServices2 = {2};
         int[] supportedServices3 = {1, 3};
         PersistableBundle carrierSupportedSatelliteServicesPerProvider = new PersistableBundle();
@@ -4066,7 +4329,11 @@
             );
         }
         mSatelliteControllerUT.setSatellitePhone(1);
+        mSatelliteControllerUT.setSelectedSatelliteSubId(SUB_ID);
+        mSatelliteControllerUT.isSatelliteProvisioned = true;
         mSatelliteControllerUT.isSatelliteAllowedCallback = null;
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.setIsSatelliteAllowedState(true);
         processAllMessages();
         mSatelliteControllerUT.elapsedRealtime = 0;
         assertTrue(mSatelliteControllerUT.isCarrierRoamingNtnEligible(mPhone));
@@ -4078,10 +4345,6 @@
         mSatelliteControllerUT.elapsedRealtime = 2 * 60 * 1000;
         moveTimeForward(2 * 60 * 1000);
         processAllMessages();
-        assertNotNull(mSatelliteControllerUT.isSatelliteAllowedCallback);
-
-        mSatelliteControllerUT.isSatelliteAllowedCallback.onResult(true);
-        processAllMessages();
         assertTrue(mSatelliteControllerUT.isCarrierRoamingNtnEligible(mPhone));
         verify(mPhone, times(1)).notifyCarrierRoamingNtnEligibleStateChanged(eq(true));
         verify(mPhone2, times(0)).notifyCarrierRoamingNtnEligibleStateChanged(anyBoolean());
@@ -4095,31 +4358,43 @@
         assertFalse(mSatelliteControllerUT.isCarrierRoamingNtnEligible(mPhone));
         verify(mPhone, times(1)).notifyCarrierRoamingNtnEligibleStateChanged(eq(false));
         verify(mPhone2, times(0)).notifyCarrierRoamingNtnEligibleStateChanged(anyBoolean());
+    }
 
-        // isSatelliteAllowedCallback.onError() returns error
-        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        sendServiceStateChangedEvent();
+    @Test
+    public void testNotifyCarrierRoamingNtnSignalStrengthChanged() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        sendSignalStrengthChangedEvent(mPhone.getPhoneId());
         processAllMessages();
-        mSatelliteControllerUT.elapsedRealtime = 0;
-        assertTrue(mSatelliteControllerUT.isCarrierRoamingNtnEligible(mPhone));
-        verify(mPhone, times(0)).notifyCarrierRoamingNtnEligibleStateChanged(eq(true));
-        verify(mPhone2, times(0)).notifyCarrierRoamingNtnEligibleStateChanged(anyBoolean());
+        ArgumentCaptor<NtnSignalStrength> captor = ArgumentCaptor.forClass(NtnSignalStrength.class);
+        verify(mPhone, times(1)).notifyCarrierRoamingNtnSignalStrengthChanged(
+                captor.capture());
+        NtnSignalStrength actualSignalStrength = captor.getValue();
+        assertEquals(NTN_SIGNAL_STRENGTH_NONE, actualSignalStrength.getLevel());
         clearInvocations(mPhone);
 
-        // 2 minutes later and hysteresis timeout is 1 minute
-        mSatelliteControllerUT.elapsedRealtime = 2 * 60 * 1000;
-        moveTimeForward(2 * 60 * 1000);
+        when(mSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        when(mPhone.getSignalStrength()).thenReturn(mSignalStrength);
+        mCarrierConfigBundle.putInt(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, 1 * 60);
+        mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
         processAllMessages();
-        assertNotNull(mSatelliteControllerUT.isSatelliteAllowedCallback);
-
-        mSatelliteControllerUT.isSatelliteAllowedCallback.onError(new SatelliteException(
-                SATELLITE_RESULT_ERROR));
+        when(mServiceState.isUsingNonTerrestrialNetwork()).thenReturn(true);
+        when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        sendServiceStateChangedEvent();
         processAllMessages();
-        assertTrue(mSatelliteControllerUT.isCarrierRoamingNtnEligible(mPhone));
-        verify(mPhone, times(0)).notifyCarrierRoamingNtnEligibleStateChanged(eq(true));
-        verify(mPhone2, times(0)).notifyCarrierRoamingNtnEligibleStateChanged(anyBoolean());
-        verify(mMockNotificationManager, times(2)).cancelAsUser(anyString(), anyInt(),
-                any());
+        captor = ArgumentCaptor.forClass(NtnSignalStrength.class);
+        verify(mPhone, times(1)).notifyCarrierRoamingNtnSignalStrengthChanged(
+                captor.capture());
+        actualSignalStrength = captor.getValue();
+        assertEquals(NTN_SIGNAL_STRENGTH_GOOD, actualSignalStrength.getLevel());
+        clearInvocations(mPhone);
     }
 
     @Test
@@ -4145,16 +4420,8 @@
                 .thenReturn(List.of(nri));
         assertTrue(mSatelliteControllerUT.getWwanIsInService(mServiceState));
 
-        nri = new NetworkRegistrationInfo.Builder()
-                .setEmergencyOnly(true)
-                .build();
-        when(mServiceState.getNetworkRegistrationInfoListForTransportType(
-                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
-                .thenReturn(List.of(nri));
-        assertTrue(mSatelliteControllerUT.getWwanIsInService(mServiceState));
-
         nri = new NetworkRegistrationInfo.Builder().setRegistrationState(
-                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING)
+                        NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING)
                 .build();
         when(mServiceState.getNetworkRegistrationInfoListForTransportType(
                 eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
@@ -4184,6 +4451,11 @@
                 resultErrorCode[0] = causeCode;
                 semaphore.release();
             }
+
+            @Override
+            public void onTerrestrialNetworkAvailableChanged(boolean isAvailable) {
+                logd("onTerrestrialNetworkAvailableChanged: isAvailable=" + isAvailable);
+            }
         };
         resetSatelliteControllerUTToSupportedAndProvisionedState();
         mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
@@ -4202,6 +4474,86 @@
         assertEquals(expectedErrorCode, resultErrorCode[0]);
     }
 
+    @RequiresFlagsDisabled(FLAG_DEVICE_STATE_PROPERTY_MIGRATION)
+    @Test
+    public void testDetermineIsFoldable_overlayConfigurationValues() {
+        // isFoldable should return false with the base configuration.
+        assertFalse(mSatelliteControllerUT.isFoldable(mContext,
+                mSatelliteControllerUT.getSupportedDeviceStates()));
+
+        mContextFixture.putIntArrayResource(R.array.config_foldedDeviceStates, new int[2]);
+        assertTrue(mSatelliteControllerUT.isFoldable(mContext,
+                mSatelliteControllerUT.getSupportedDeviceStates()));
+    }
+
+    @RequiresFlagsEnabled(FLAG_DEVICE_STATE_PROPERTY_MIGRATION)
+    @Test
+    public void testDetermineIsFoldable_deviceStateManager() {
+        // isFoldable should return false with the base configuration.
+        assertFalse(mSatelliteControllerUT.isFoldable(mContext,
+                mSatelliteControllerUT.getSupportedDeviceStates()));
+
+        DeviceState foldedDeviceState = new DeviceState(new DeviceState.Configuration.Builder(
+                0 /* identifier */, "FOLDED")
+                .setSystemProperties(Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY))
+                .setPhysicalProperties(
+                        Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_CLOSED))
+                .build());
+        DeviceState unfoldedDeviceState = new DeviceState(new DeviceState.Configuration.Builder(
+                1 /* identifier */, "UNFOLDED")
+                .setSystemProperties(Set.of(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_INNER_PRIMARY))
+                .setPhysicalProperties(
+                        Set.of(PROPERTY_FOLDABLE_HARDWARE_CONFIGURATION_FOLD_IN_OPEN))
+                .build());
+        List<DeviceState> foldableDeviceStateList = List.of(foldedDeviceState, unfoldedDeviceState);
+        assertTrue(mSatelliteControllerUT.isFoldable(mContext, foldableDeviceStateList));
+    }
+
+    @Test
+    public void testTerrestrialNetworkAvailableChangedCallback() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        Semaphore semaphore = new Semaphore(0);
+        final int[] receivedScanResult = new int[1];
+        ISatelliteModemStateCallback callback = new ISatelliteModemStateCallback.Stub() {
+            @Override
+            public void onSatelliteModemStateChanged(int state) {
+                logd("onSatelliteModemStateChanged: state=" + state);
+            }
+
+            @Override
+            public void onEmergencyModeChanged(boolean isEmergency) {
+                logd("onEmergencyModeChanged: emergency=" + isEmergency);
+            }
+
+            @Override
+            public void onRegistrationFailure(int causeCode) {
+                logd("onRegistrationFailure: causeCode=" + causeCode);
+            }
+
+            @Override
+            public void onTerrestrialNetworkAvailableChanged(boolean isAvailable) {
+                logd("onTerrestrialNetworkAvailableChanged: isAvailable=" + isAvailable);
+                receivedScanResult[0] = isAvailable ? 1 : 0;
+                semaphore.release();
+            }
+        };
+        resetSatelliteControllerUTToSupportedAndProvisionedState();
+        mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
+
+        int RegisterErrorCode = mSatelliteControllerUT.registerForSatelliteModemStateChanged(
+                callback);
+        assertEquals(SATELLITE_RESULT_SUCCESS, RegisterErrorCode);
+        verify(mMockSatelliteSessionController).registerForSatelliteModemStateChanged(callback);
+
+        int expectedErrorCode = 1;
+        mIIntegerConsumerResults.clear();
+        sendTerrestrialNetworkAvailableChangedEvent(true, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegistrationFailureCallback"));
+        assertEquals(expectedErrorCode, receivedScanResult[0]);
+    }
+
     private boolean mProvisionState = false;
     private int mProvisionSateResultCode = -1;
     private Semaphore mProvisionSateSemaphore = new Semaphore(0);
@@ -4270,8 +4622,9 @@
     }
 
     private void verifyRequestSatelliteSubscriberProvisionStatus() throws Exception {
-        setSatelliteSubscriberTesting();
+        setSatelliteSubscriberTesting(true);
         List<SatelliteSubscriberInfo> list = getExpectedSatelliteSubscriberInfoList();
+        mCarrierConfigBundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         mCarrierConfigBundle.putString(KEY_SATELLITE_NIDD_APN_NAME_STRING, mNiddApn);
         mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL, true);
         for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
@@ -4318,7 +4671,6 @@
         assertTrue(mProvisionState);
     }
 
-
     @Test
     public void testRegisterForSatelliteSubscriptionProvisionStateChanged() throws Exception {
         when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
@@ -4349,6 +4701,18 @@
                 }
             }
         };
+
+        TestSubscriptionManager testSubscriptionManager = new TestSubscriptionManager();
+        doAnswer(invocation -> {
+            testSubscriptionManager.setIsSatelliteProvisionedForNonIpDatagram(
+                    invocation.getArgument(0), invocation.getArgument(1));
+            return null;
+        }).when(mMockSubscriptionManagerService).setIsSatelliteProvisionedForNonIpDatagram(anyInt(),
+                anyBoolean());
+        doAnswer(invocation -> testSubscriptionManager.isSatelliteProvisionedForNonIpDatagram(
+                invocation.getArgument(0))).when(
+                mMockSubscriptionManagerService).isSatelliteProvisionedForNonIpDatagram(anyInt());
+
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         int errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(callback);
@@ -4368,7 +4732,7 @@
                 any());
         assertTrue(waitForForEvents(
                 semaphore, 1, "testRegisterForSatelliteSubscriptionProvisionStateChanged"));
-        assertTrue(resultArray[0].getProvisionStatus());
+        assertTrue(resultArray[0].isProvisioned());
         assertEquals(mSubscriberId, resultArray[0].getSatelliteSubscriberInfo().getSubscriberId());
 
         // Request provisioning with SatelliteSubscriberInfo that has not been provisioned
@@ -4377,43 +4741,128 @@
         inputList.add(list.get(1));
         verifyProvisionSatellite(inputList);
 
-        verify(mMockSatelliteModemInterface, times(2)).updateSatelliteSubscription(anyString(),
-                any());
         assertTrue(waitForForEvents(
                 semaphore, 1, "testRegisterForSatelliteSubscriptionProvisionStateChanged"));
-        assertTrue(resultArray[1].getProvisionStatus());
+        assertTrue(resultArray[1].isProvisioned());
         assertEquals(mSubscriberId2, resultArray[1].getSatelliteSubscriberInfo().getSubscriberId());
 
         // Request provisioning with the same SatelliteSubscriberInfo that was previously
         // requested, and verify that onSatelliteSubscriptionProvisionStateChanged is not called.
         verifyProvisionSatellite(inputList);
 
-        verify(mMockSatelliteModemInterface, times(2)).updateSatelliteSubscription(anyString(),
-                any());
         assertFalse(waitForForEvents(
                 semaphore, 1, "testRegisterForSatelliteSubscriptionProvisionStateChanged"));
+
+        // Request deprovision for subscriberID 2, verify that subscriberID 2 is set to
+        // deprovision and that subscriberID 1 is set to provision.
+        verifyDeprovisionSatellite(inputList);
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteSubscriptionProvisionStateChanged"));
+        assertFalse(resultArray[1].isProvisioned());
+        assertEquals(mSubscriberId2, resultArray[1].getSatelliteSubscriberInfo().getSubscriberId());
+        assertTrue(resultArray[0].isProvisioned());
+        assertEquals(mSubscriberId, resultArray[0].getSatelliteSubscriberInfo().getSubscriberId());
+
+        // Request deprovision for subscriberID 1, verify that subscriberID 1 is set to deprovision.
+        inputList = new ArrayList<>();
+        inputList.add(list.get(0));
+        verifyDeprovisionSatellite(inputList);
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteSubscriptionProvisionStateChanged"));
+        assertFalse(resultArray[1].isProvisioned());
+        assertEquals(mSubscriberId2, resultArray[1].getSatelliteSubscriberInfo().getSubscriberId());
+        assertFalse(resultArray[0].isProvisioned());
+        assertEquals(mSubscriberId, resultArray[0].getSatelliteSubscriberInfo().getSubscriberId());
+
+        // Request provision for subscriberID 2, verify that subscriberID 2 is set to provision.
+        inputList = new ArrayList<>();
+        inputList.add(list.get(1));
+        verifyProvisionSatellite(inputList);
+
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteSubscriptionProvisionStateChanged"));
+        assertTrue(resultArray[1].isProvisioned());
+        assertEquals(mSubscriberId2, resultArray[1].getSatelliteSubscriberInfo().getSubscriberId());
+        assertFalse(resultArray[0].isProvisioned());
+        assertEquals(mSubscriberId, resultArray[0].getSatelliteSubscriberInfo().getSubscriberId());
     }
 
-    private void setSatelliteSubscriberTesting() throws Exception {
+    private boolean mDeprovisionDone = false;
+    private int mDeprovisionSateResultCode = -1;
+    private Semaphore mDeprovisionSateSemaphore = new Semaphore(0);
+    private ResultReceiver mDeprovisionSatelliteReceiver = new ResultReceiver(null) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mDeprovisionSateResultCode = resultCode;
+            logd("DeprovisionSatelliteReceiver: resultCode=" + resultCode);
+            if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                if (resultData.containsKey(KEY_DEPROVISION_SATELLITE_TOKENS)) {
+                    mDeprovisionDone = resultData.getBoolean(KEY_DEPROVISION_SATELLITE_TOKENS);
+                    logd("DeprovisionSatelliteReceiver: deprovision=" + mDeprovisionDone);
+                } else {
+                    loge("KEY_DEPROVISION_SATELLITE_TOKENS does not exist.");
+                    mDeprovisionDone = false;
+                }
+            } else {
+                mDeprovisionDone = false;
+            }
+            try {
+                mDeprovisionSateSemaphore.release();
+            } catch (Exception ex) {
+                loge("DeprovisionSatelliteReceiver: Got exception in releasing semaphore " + ex);
+            }
+        }
+    };
+
+    @Test
+    public void testDeprovisionSatellite() throws Exception {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        verifyRequestSatelliteSubscriberProvisionStatus();
+        List<SatelliteSubscriberInfo> inputList = getExpectedSatelliteSubscriberInfoList();
+        verifyProvisionSatellite(inputList);
+        verifyDeprovisionSatellite(inputList);
+    }
+
+    private void verifyDeprovisionSatellite(List<SatelliteSubscriberInfo> inputList) {
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[1];
+            AsyncResult.forMessage(message, null, new SatelliteException(SATELLITE_RESULT_SUCCESS));
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).updateSatelliteSubscription(anyString(), any());
+
+        mSatelliteControllerUT.deprovisionSatellite(inputList, mDeprovisionSatelliteReceiver);
+        processAllMessages();
+        assertEquals(SATELLITE_RESULT_SUCCESS, mDeprovisionSateResultCode);
+        assertTrue(mDeprovisionDone);
+    }
+
+    private void setSatelliteSubscriberTesting(boolean sameCarrier) throws Exception {
         doReturn("123").when(mContext).getAttributionTag();
-        final int carrierId = 0;
+        final int carrierId_subID = 0;
+        final int carrierId_subID1 = sameCarrier ? 0 : 1;
         SubscriptionInfo subscriptionInfo = new SubscriptionInfo.Builder()
                 .setId(SUB_ID).setIccId(mIccId).setSimSlotIndex(0).setOnlyNonTerrestrialNetwork(
-                        false).setSatelliteESOSSupported(true).setCarrierId(carrierId).build();
+                        false).setSatelliteESOSSupported(true).setCarrierId(
+                            carrierId_subID).build();
         SubscriptionInfo subscriptionInfo2 = new SubscriptionInfo.Builder()
                 .setId(SUB_ID1).setIccId(mIccId2).setSimSlotIndex(1).setOnlyNonTerrestrialNetwork(
-                        true).setSatelliteESOSSupported(false).setCarrierId(carrierId).build();
+                        true).setSatelliteESOSSupported(false).setCarrierId(
+                            carrierId_subID1).build();
         List<SubscriptionInfo> allSubInfos = new ArrayList<>();
         allSubInfos.add(subscriptionInfo);
         allSubInfos.add(subscriptionInfo2);
+        testSubscriptionInfo = subscriptionInfo;
+        testSubscriptionInfo2 = subscriptionInfo2;
         doReturn(allSubInfos).when(mMockSubscriptionManagerService).getAllSubInfoList(
                 anyString(), anyString());
         SubscriptionInfoInternal subInfoInternal =
-                new SubscriptionInfoInternal.Builder().setCarrierId(0).setImsi(mImsi).setIccId(
-                        mIccId).build();
+                new SubscriptionInfoInternal.Builder().setCarrierId(
+                    carrierId_subID).setImsi(mImsi).setIccId(mIccId).build();
         SubscriptionInfoInternal subInfoInternal2 =
-                new SubscriptionInfoInternal.Builder().setCarrierId(0).setImsi(mImsi2).setIccId(
-                        mIccId2).build();
+                new SubscriptionInfoInternal.Builder().setCarrierId(
+                    carrierId_subID1).setImsi(mImsi2).setIccId(mIccId2).build();
         doReturn(subscriptionInfo).when(mMockSubscriptionManagerService).getSubscriptionInfo(
                 eq(SUB_ID));
         doReturn(subscriptionInfo2).when(mMockSubscriptionManagerService).getSubscriptionInfo(
@@ -4508,6 +4957,7 @@
     @Test
     public void testCheckForSubscriberIdChange_changed() {
         when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mCarrierConfigBundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         List<SubscriptionInfo> allSubInfos = new ArrayList<>();
 
         String imsi = "012345";
@@ -4526,6 +4976,7 @@
                 .thenReturn(allSubInfos);
 
         when(mSubscriptionInfo.isSatelliteESOSSupported()).thenReturn(true);
+        when(mSubscriptionInfo.isActive()).thenReturn(true);
         when(mMockSubscriptionManagerService.getSubscriptionInfoInternal(SUB_ID))
                 .thenReturn(subInfoInternal);
 
@@ -4555,10 +5006,79 @@
     }
 
     @Test
+    public void testRegisterForSatelliteCommunicationAllowedStateChanged() throws Exception {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mContextFixture.putIntArrayResource(
+                R.array.config_verizon_satellite_enabled_tagids,
+                new int[]{1001});
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getIntArray(
+                R.array.config_verizon_satellite_enabled_tagids)).thenReturn(new int[]{1001});
+        // carrierID is same as SUBID for this test
+        final int carrierSubId = SUB_ID;
+        final int oemSubId = SUB_ID1;
+        final String carrierSubscriberId = mSubscriberId;
+        final String oemSubscriberId = mSubscriberId2;
+        mCarrierConfigBundle.putBoolean(KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        setSatelliteSubscriberTesting(false);
+        invokeCarrierConfigChanged();
+
+        Field provisionedSubscriberIdField = SatelliteController.class.getDeclaredField(
+                "mProvisionedSubscriberId");
+        provisionedSubscriberIdField.setAccessible(true);
+        Map<String, Boolean> testProvisionedSubscriberId = new HashMap<>();;
+        testProvisionedSubscriberId.put(carrierSubscriberId, true);
+        testProvisionedSubscriberId.put(oemSubscriberId, true);
+        provisionedSubscriberIdField.set(mSatelliteControllerUT, testProvisionedSubscriberId);
+
+        Field currentLocationTagIdsField = SatelliteController.class.getDeclaredField(
+                "mCurrentLocationTagIds");
+        currentLocationTagIdsField.setAccessible(true);
+
+        setComponentName();
+        mSatelliteControllerUT.setIsSatelliteAllowedState(true);
+
+        mSatelliteControllerUT.registerForSatelliteCommunicationAllowedStateChanged();
+
+        // Test satelliteAccessConfigCallback.onSuccess
+        // with current location NOT supporting carrier satellite
+        // OEM satellite subscription should be selected
+        currentLocationTagIdsField.set(mSatelliteControllerUT, Arrays.asList(100));
+
+        mSatelliteControllerUT.subsInfoListPerPriority().computeIfAbsent(
+                        getKeyPriority(testSubscriptionInfo), k -> new ArrayList<>())
+                .add(testSubscriptionInfo);
+        mSatelliteControllerUT.subsInfoListPerPriority().computeIfAbsent(
+                        getKeyPriority(testSubscriptionInfo2), k -> new ArrayList<>())
+                .add(testSubscriptionInfo2);
+
+        mSatelliteControllerUT.evaluateESOSProfilesPrioritizationTest();
+        processAllMessages();
+        assertEquals(oemSubId, mSatelliteControllerUT.getSelectedSatelliteSubId());
+
+        // Test satelliteAccessConfigCallback.onSuccess
+        // with current location supporting carrier satellite
+        // Carrier satellite subscription should be selected
+        currentLocationTagIdsField.set(mSatelliteControllerUT, Arrays.asList(1001, 100));
+
+        mSatelliteControllerUT.subsInfoListPerPriority().computeIfAbsent(
+                        getKeyPriority(testSubscriptionInfo), k -> new ArrayList<>())
+                .add(testSubscriptionInfo);
+        mSatelliteControllerUT.subsInfoListPerPriority().computeIfAbsent(
+                        getKeyPriority(testSubscriptionInfo2), k -> new ArrayList<>())
+                .add(testSubscriptionInfo2);
+
+        mSatelliteControllerUT.evaluateESOSProfilesPrioritizationTest();
+        processAllMessages();
+        assertEquals(carrierSubId, mSatelliteControllerUT.getSelectedSatelliteSubId());
+    }
+
+
+    @Test
     public void testProvisionStatusPerSubscriberIdGetFromDb() throws Exception {
         when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
 
-        setSatelliteSubscriberTesting();
+        setSatelliteSubscriberTesting(true);
         // Check if the cache is not updated when the value read from the database is false.
         verifyProvisionStatusPerSubscriberIdGetFromDb(false);
 
@@ -4570,7 +5090,7 @@
     public void testProvisionStatusPerSubscriberIdStoreToDb() throws Exception {
         when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
 
-        setSatelliteSubscriberTesting();
+        setSatelliteSubscriberTesting(true);
         // Check if the cache is not updated when the value read from the database is false.
         verifyProvisionStatusPerSubscriberIdGetFromDb(false);
 
@@ -4580,6 +5100,260 @@
                 eq(SUB_ID), eq(true));
     }
 
+    @Test
+    public void testIsCarrierRoamingNtnAvailableServicesForManualConnect() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
+        // CARRIER_ROAMING_NTN_CONNECT_MANUAL: 1
+        mCarrierConfigBundle.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 1);
+
+        mSatelliteControllerUT.setSatellitePhone(1);
+        processAllMessages();
+        when(mContext.getPackageManager()).thenReturn(mMockPManager);
+        try {
+            when(mMockPManager.getApplicationInfo(anyString(),
+                    anyInt())).thenReturn(getApplicationInfo());
+        } catch (PackageManager.NameNotFoundException e) {
+            logd("NameNotFoundException");
+        }
+        assertTrue(mSatelliteControllerUT
+                .isP2PSmsDisallowedOnCarrierRoamingNtn(/*subId*/ SUB_ID));
+    }
+
+    @Test
+    public void testIsCarrierRoamingNtnAvailableServicesForAutomaticConnect() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
+        // CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC: 0
+        mCarrierConfigBundle.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 0);
+
+        mSatelliteControllerUT.setSatellitePhone(1);
+        processAllMessages();
+        when(mContext.getPackageManager()).thenReturn(mMockPManager);
+        try {
+            when(mMockPManager.getApplicationInfo(anyString(),
+                    anyInt())).thenReturn(getApplicationInfo());
+        } catch (PackageManager.NameNotFoundException e) {
+            logd("NameNotFoundException");
+        }
+        // If it is automatic connection case, it is not support the callback.
+        assertFalse(mSatelliteControllerUT
+                .isP2PSmsDisallowedOnCarrierRoamingNtn(/*subId*/ SUB_ID));
+    }
+
+    ApplicationInfo getApplicationInfo() {
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.metaData = new Bundle();
+        applicationInfo.metaData.putBoolean(
+                METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT, true);
+        return applicationInfo;
+    }
+
+    @Test
+    public void testRegisterApplicationStateChanged() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, false);
+        when(mMockSubscriptionManagerService.getActiveSubIdList(true))
+                .thenReturn(new int[]{SUB_ID1});
+
+        ArgumentCaptor<IntentFilter> intentFilterCaptor =
+                ArgumentCaptor.forClass(IntentFilter.class);
+        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext).registerReceiver(receiverCaptor.capture(), intentFilterCaptor.capture(),
+                anyInt());
+
+        BroadcastReceiver receiver = receiverCaptor.getValue();
+        mSatelliteControllerUT =
+                new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
+        assertFalse(mSatelliteControllerUT.isApplicationUpdated);
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+        intent.setData(Uri.parse("com.example.app"));
+        receiver.onReceive(mContext, intent);
+        CountDownLatch latch1 = new CountDownLatch(1);
+        new Handler(Looper.getMainLooper()).postDelayed(() -> {
+            latch1.countDown();
+        }, 100);
+        try {
+            latch1.await();
+        } catch (InterruptedException e) {
+        }
+        assertTrue(mSatelliteControllerUT.isApplicationUpdated);
+        mSatelliteControllerUT =
+                new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
+        assertFalse(mSatelliteControllerUT.isApplicationUpdated);
+        intent = new Intent(Intent.ACTION_PACKAGE_REPLACED);
+        intent.setData(Uri.parse("com.example.app"));
+        receiver.onReceive(mContext, intent);
+        CountDownLatch latch2 = new CountDownLatch(1);
+        new Handler(Looper.getMainLooper()).postDelayed(() -> {
+            latch2.countDown();
+        }, 100);
+        try {
+            latch2.await();
+        } catch (InterruptedException e) {
+        }
+        assertTrue(mSatelliteControllerUT.isApplicationUpdated);
+        mSatelliteControllerUT =
+                new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
+        assertFalse(mSatelliteControllerUT.isApplicationUpdated);
+        intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+        intent.setData(Uri.parse("com.example.app"));
+        receiver.onReceive(mContext, intent);
+        CountDownLatch latch3 = new CountDownLatch(1);
+        new Handler(Looper.getMainLooper()).postDelayed(() -> {
+            latch3.countDown();
+        }, 100);
+        try {
+            latch3.await();
+        } catch (InterruptedException e) {
+        }
+        assertTrue(mSatelliteControllerUT.isApplicationUpdated);
+        mSatelliteControllerUT =
+                new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
+        assertFalse(mSatelliteControllerUT.isApplicationUpdated);
+        intent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+        intent.setData(Uri.parse("com.example.different"));
+        receiver.onReceive(mContext, intent);
+        CountDownLatch latch4 = new CountDownLatch(1);
+        new Handler(Looper.getMainLooper()).postDelayed(() -> {
+            latch4.countDown();
+        }, 100);
+        try {
+            latch4.await();
+        } catch (InterruptedException e) {
+        }
+        assertFalse(mSatelliteControllerUT.isApplicationUpdated);
+    }
+
+    @Test
+    public void testUpdateSystemSelectionChannels() {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+
+        String mccmnc = "123455";
+        int[] bands1 = {200, 201, 202};
+        IntArray intArraybands1 = new IntArray(3);
+        intArraybands1.addAll(bands1);
+        int[] earfcns1 = {300, 301, 310, 311};
+        IntArray intArrayEarfcns1 = new IntArray(4);
+        intArrayEarfcns1.addAll(earfcns1);
+        String seed1 = "test-seed-satellite1";
+        UUID uuid1 = UUID.nameUUIDFromBytes(seed1.getBytes());
+        SatellitePosition satellitePosition1 = new SatellitePosition(0, 35876);
+        EarfcnRange earfcnRange1 = new EarfcnRange(301, 300);
+        EarfcnRange earfcnRange2 = new EarfcnRange(311, 310);
+        List<EarfcnRange> earfcnRangeList1 = new ArrayList<>(
+                Arrays.asList(earfcnRange1, earfcnRange2));
+        SatelliteInfo satelliteInfo1 = new SatelliteInfo(uuid1, satellitePosition1, Arrays.stream(
+                bands1).boxed().collect(Collectors.toList()), earfcnRangeList1);
+        int[] tagIds = {1, 2, 3};
+        IntArray intArrayTagIds = new IntArray(3);
+        intArrayTagIds.addAll(tagIds);
+        SystemSelectionSpecifier systemSelectionSpecifier1 = new SystemSelectionSpecifier(mccmnc,
+                intArraybands1, intArrayEarfcns1, new SatelliteInfo[]{satelliteInfo1},
+                intArrayTagIds);
+
+        setUpResponseForUpdateSystemSelectionChannels(SATELLITE_RESULT_ERROR);
+        mSatelliteControllerUT.updateSystemSelectionChannels(
+                new ArrayList<>(List.of(systemSelectionSpecifier1)),
+                mSystemSelectionChannelUpdatedReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestUpdateSystemSelectionChannelResult(1));
+        assertEquals(SATELLITE_RESULT_ERROR, mQueriedSystemSelectionChannelUpdatedResultCode);
+
+        // Verify whether callback receives expected result
+        setUpResponseForUpdateSystemSelectionChannels(SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.updateSystemSelectionChannels(
+                new ArrayList<>(List.of(systemSelectionSpecifier1)),
+                mSystemSelectionChannelUpdatedReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestUpdateSystemSelectionChannelResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSystemSelectionChannelUpdatedResultCode);
+
+        // Verify whether SatelliteModemInterface API was invoked and data is valid, when single
+        // data was provided.
+        ArgumentCaptor<List<SystemSelectionSpecifier>> systemSelectionSpecifierListCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mMockSatelliteModemInterface, times(2)).updateSystemSelectionChannels(
+                systemSelectionSpecifierListCaptor.capture(), any(Message.class));
+        List<SystemSelectionSpecifier> capturedList = systemSelectionSpecifierListCaptor.getValue();
+        SystemSelectionSpecifier systemSelectionSpecifier = capturedList.getFirst();
+
+        assertEquals(mccmnc, systemSelectionSpecifier.getMccMnc());
+        int[] actualBandsArray = systemSelectionSpecifier.getBands();
+        assertArrayEquals(bands1, actualBandsArray);
+        int[] actualEarfcnsArray = systemSelectionSpecifier.getEarfcns();
+        assertArrayEquals(earfcns1, actualEarfcnsArray);
+        assertArrayEquals(new SatelliteInfo[]{satelliteInfo1},
+                systemSelectionSpecifier.getSatelliteInfos().toArray(new SatelliteInfo[0]));
+        int[] actualTagIdArray = systemSelectionSpecifier.getTagIds();
+        assertArrayEquals(tagIds, actualTagIdArray);
+
+        // Verify whether SatelliteModemInterface API was invoked and data is valid, when list
+        // of data was provided.
+        int[] bands2 = {210, 211, 212};
+        IntArray intArraybands2 = new IntArray(3);
+        intArraybands2.addAll(bands2);
+        int[] earfcns2 = {320, 321, 330, 331};
+        IntArray intArrayEarfcns2 = new IntArray(4);
+        intArrayEarfcns2.addAll(earfcns2);
+        String seed2 = "test-seed-satellite2";
+        UUID uuid2 = UUID.nameUUIDFromBytes(seed2.getBytes());
+        SatellitePosition satellitePosition2 = new SatellitePosition(120, 35876);
+        EarfcnRange earfcnRange3 = new EarfcnRange(321, 320);
+        EarfcnRange earfcnRange4 = new EarfcnRange(331, 330);
+        List<EarfcnRange> earfcnRangeList2 = new ArrayList<>(
+                Arrays.asList(earfcnRange3, earfcnRange4));
+        SatelliteInfo satelliteInfo2 = new SatelliteInfo(uuid2, satellitePosition2, Arrays.stream(
+                bands1).boxed().collect(Collectors.toList()), earfcnRangeList2);
+        SystemSelectionSpecifier systemSelectionSpecifier2 = new SystemSelectionSpecifier(mccmnc,
+                intArraybands2, intArrayEarfcns2, new SatelliteInfo[]{satelliteInfo2},
+                intArrayTagIds);
+
+        // Verify whether callback receives expected result
+        setUpResponseForUpdateSystemSelectionChannels(SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.updateSystemSelectionChannels(
+                new ArrayList<>(List.of(systemSelectionSpecifier1, systemSelectionSpecifier2)),
+                mSystemSelectionChannelUpdatedReceiver);
+        processAllMessages();
+        assertTrue(waitForRequestUpdateSystemSelectionChannelResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSystemSelectionChannelUpdatedResultCode);
+
+        // Verify whether SatelliteModemInterface API was invoked and data is valid,
+        verify(mMockSatelliteModemInterface, times(3)).updateSystemSelectionChannels(
+                systemSelectionSpecifierListCaptor.capture(), any(Message.class));
+        capturedList = systemSelectionSpecifierListCaptor.getValue();
+        SystemSelectionSpecifier capturedSystemSelectionSpecifier1 = capturedList.getFirst();
+        SystemSelectionSpecifier capturedSystemSelectionSpecifier2 = capturedList.get(1);
+
+        // Verify first SystemSelectionSpecifier
+        assertEquals(mccmnc, systemSelectionSpecifier.getMccMnc());
+        actualBandsArray = capturedSystemSelectionSpecifier1.getBands();
+        assertArrayEquals(bands1, actualBandsArray);
+        actualEarfcnsArray = capturedSystemSelectionSpecifier1.getEarfcns();
+        assertArrayEquals(earfcns1, actualEarfcnsArray);
+        assertArrayEquals(new SatelliteInfo[]{satelliteInfo1},
+                capturedSystemSelectionSpecifier1.getSatelliteInfos().toArray(
+                        new SatelliteInfo[0]));
+        actualTagIdArray = capturedSystemSelectionSpecifier1.getTagIds();
+        assertArrayEquals(tagIds, actualTagIdArray);
+
+        // Verify second SystemSelectionSpecifier
+        assertEquals(mccmnc, systemSelectionSpecifier.getMccMnc());
+        actualBandsArray = capturedSystemSelectionSpecifier2.getBands();
+        assertArrayEquals(bands2, actualBandsArray);
+        actualEarfcnsArray = capturedSystemSelectionSpecifier2.getEarfcns();
+        assertArrayEquals(earfcns2, actualEarfcnsArray);
+        assertArrayEquals(new SatelliteInfo[]{satelliteInfo2},
+                capturedSystemSelectionSpecifier2.getSatelliteInfos().toArray(
+                        new SatelliteInfo[0]));
+        actualTagIdArray = capturedSystemSelectionSpecifier2.getTagIds();
+        assertArrayEquals(tagIds, actualTagIdArray);
+    }
+
     private void verifyProvisionStatusPerSubscriberIdGetFromDb(boolean provision) {
         doReturn(provision).when(
                 mMockSubscriptionManagerService).isSatelliteProvisionedForNonIpDatagram(anyInt());
@@ -4600,7 +5374,7 @@
         assertEquals(SATELLITE_RESULT_SUCCESS,
                 mRequestSatelliteSubscriberProvisionStatusResultCode);
         assertEquals(provision,
-                mRequestSatelliteSubscriberProvisionStatusResultList.get(0).getProvisionStatus());
+                mRequestSatelliteSubscriberProvisionStatusResultList.get(0).isProvisioned());
     }
 
     private void setComponentName() {
@@ -4616,9 +5390,12 @@
         boolean isActive = subscriptionInfo.isActive();
         boolean isNtnOnly = subscriptionInfo.isOnlyNonTerrestrialNetwork();
         boolean isESOSSupported = subscriptionInfo.isSatelliteESOSSupported();
+        boolean isCarrierSatelliteHigherPriority =
+                mSatelliteControllerUT.isCarrierSatelliteHigherPriorityTest(
+                        subscriptionInfo);
 
         int keyPriority;
-        if (isESOSSupported && isActive) {
+        if (isESOSSupported && isActive && isCarrierSatelliteHigherPriority) {
             keyPriority = 1;
         } else if (isNtnOnly) {
             keyPriority = 2;
@@ -4704,6 +5481,7 @@
             message.sendToTarget();
             return null;
         }).when(mMockSatelliteModemInterface).requestIsSatelliteEnabled(any(Message.class));
+        mSatelliteControllerUT.isSatelliteEnabledOrBeingEnabled = isSatelliteEnabled;
     }
 
     private void setUpResponseForRequestIsSatelliteSupported(
@@ -4894,6 +5672,19 @@
         }).when(mMockSatelliteModemInterface).stopSendingNtnSignalStrength(any(Message.class));
     }
 
+    private void setUpResponseForUpdateSystemSelectionChannels(
+            @SatelliteManager.SatelliteResult int error) {
+        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
+                ? null : new SatelliteException(error);
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[1];
+            AsyncResult.forMessage(message, null, exception);
+            message.sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).updateSystemSelectionChannels(anyList(),
+                any(Message.class));
+    }
+
     private boolean waitForRequestIsSatelliteSupportedResult(int expectedNumberOfEvents) {
         for (int i = 0; i < expectedNumberOfEvents; i++) {
             try {
@@ -5017,6 +5808,24 @@
         return true;
     }
 
+    private boolean waitForRequestUpdateSystemSelectionChannelResult(int expectedNumberOfEvents) {
+        for (int i = 0; i < expectedNumberOfEvents; i++) {
+            try {
+                if (!mSystemSelectionChannelUpdatedSemaphore.tryAcquire(TIMEOUT,
+                        TimeUnit.MILLISECONDS)) {
+                    logd("Timeout to receive "
+                            + "updateSystemSelectionChannel()"
+                            + " callback");
+                    return false;
+                }
+            } catch (Exception ex) {
+                logd("updateSystemSelectionChannel: Got exception=" + ex);
+                return false;
+            }
+        }
+        return true;
+    }
+
     private void verifySatelliteSupported(boolean supported, int expectedErrorCode) {
         mSatelliteSupportSemaphore.drainPermits();
         mSatelliteControllerUT.requestIsSatelliteSupported(mSatelliteSupportReceiver);
@@ -5086,6 +5895,13 @@
         msg.sendToTarget();
     }
 
+    private void sendSignalStrengthChangedEvent(int phoneId) {
+        Message msg = mSatelliteControllerUT.obtainMessage(
+                57 /* EVENT_SIGNAL_STRENGTH_CHANGED */);
+        msg.obj = new AsyncResult(phoneId, null, null);
+        msg.sendToTarget();
+    }
+
     private void sendCmdStartSendingNtnSignalStrengthChangedEvent(boolean shouldReport) {
         Message msg = mSatelliteControllerUT.obtainMessage(
                 35 /* CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING */);
@@ -5129,6 +5945,27 @@
         msg.sendToTarget();
     }
 
+    private void sendTerrestrialNetworkAvailableChangedEvent(boolean isAvailable,
+            Throwable exception) {
+        Message msg = mSatelliteControllerUT.obtainMessage(
+                55 /* EVENT_TERRESTRIAL_NETWORK_AVAILABLE_CHANGED */);
+        msg.obj = new AsyncResult(null, isAvailable, exception);
+        msg.sendToTarget();
+    }
+
+    private void sendSelectedNbIotSatelliteSubscriptionChangedEvent(int selectedSubId,
+            Throwable exception) {
+        Message msg = mSatelliteControllerUT.obtainMessage(
+                60 /* EVENT_SELECTED_NB_IOT_SATELLITE_SUBSCRIPTION_CHANGED */);
+        msg.obj = new AsyncResult(null, selectedSubId, exception);
+        msg.sendToTarget();
+    }
+
+    private void sendCmdEvaluateCarrierRoamingNtnEligibilityChange() {
+        mSatelliteControllerUT.obtainMessage(
+                61 /* CMD_EVALUATE_CARRIER_ROAMING_NTN_ELIGIBILITY_CHANGE */).sendToTarget();
+    }
+
     private void setRadioPower(boolean on) {
         mSimulatedCommands.setRadioPower(on, false, false, null);
     }
@@ -5158,6 +5995,15 @@
         setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+
+        try {
+            setSatelliteSubscriberTesting(true);
+        } catch (Exception ex) {
+            fail("provisionSatelliteService.setSatelliteSubscriberTesting: ex=" + ex);
+        }
+        doReturn(true).when(mMockSubscriptionManagerService).isSatelliteProvisionedForNonIpDatagram(
+                anyInt());
+
         cancelRemote = mSatelliteControllerUT.provisionSatelliteService(
                 TEST_SATELLITE_TOKEN,
                 testProvisionData, mIIntegerConsumer);
@@ -5329,11 +6175,19 @@
         public int satelliteModeSettingValue = SATELLITE_MODE_ENABLED_FALSE;
         public boolean setSettingsKeyToAllowDeviceRotationCalled = false;
         public OutcomeReceiver<Boolean, SatelliteException> isSatelliteAllowedCallback = null;
+        public static boolean isApplicationUpdated;
+        public String packageName = "com.example.app";
+        public boolean isSatelliteBeingDisabled = false;
+        public boolean mIsApplicationSupportsP2P = false;
+        public int selectedSatelliteSubId = -1;
+        public boolean isSatelliteProvisioned;
+        public boolean isSatelliteEnabledOrBeingEnabled = false;
 
         TestSatelliteController(
                 Context context, Looper looper, @NonNull FeatureFlags featureFlags) {
             super(context, looper, featureFlags);
             logd("Constructing TestSatelliteController");
+            isApplicationUpdated = false;
         }
 
         @Override
@@ -5372,6 +6226,11 @@
             return elapsedRealtime;
         }
 
+        @Override
+        protected void registerForSatelliteCommunicationAllowedStateChanged() {
+            logd("registerForSatelliteCommunicationAllowedStateChanged");
+        }
+
         void setSatelliteSessionController(SatelliteSessionController satelliteSessionController) {
             mSatelliteSessionController = satelliteSessionController;
         }
@@ -5385,26 +6244,64 @@
         }
 
         @Override
-        protected void requestIsSatelliteCommunicationAllowedForCurrentLocation(
-                @NonNull OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> callback) {
-            logd("requestIsSatelliteCommunicationAllowedForCurrentLocation: callback="
-                    + callback);
-            isSatelliteAllowedCallback = callback;
+        protected void setSelectedSatelliteSubId(int subId) {
+            logd("setSelectedSatelliteSubId: subId=" + subId);
+            synchronized (mSatelliteTokenProvisionedLock) {
+                mSelectedSatelliteSubId = subId;
+            }
         }
 
         @Override
         protected boolean isSubscriptionProvisioned(int subId) {
-            synchronized (mSatellitePhoneLock) {
-                if (mSatellitePhone.getSubId() == subId) {
-                    return true;
-                }
-            }
-            return false;
+            return isSatelliteProvisioned;
         }
 
+        @Override
+        protected List<DeviceState> getSupportedDeviceStates() {
+            return List.of(new DeviceState(new DeviceState.Configuration.Builder(0 /* identifier */,
+                    "DEFAULT" /* name */).build()));
+        }
+
+        @Override
+        public boolean isSatelliteBeingDisabled() {
+            return isSatelliteBeingDisabled;
+        }
+
+        @Override
+        public boolean isSatelliteEnabledOrBeingEnabled() {
+            return isSatelliteEnabledOrBeingEnabled;
+        }
+
+        protected String getConfigSatelliteGatewayServicePackage() {
+            String packageName = "com.example.app";
+            return packageName;
+        }
+
+        @Override
+        protected void handleCarrierRoamingNtnAvailableServicesChanged(int subId) {
+            isApplicationUpdated = true;
+        }
+
+        @Override
+        public boolean isApplicationSupportsP2P(String packageName) {
+            return mIsApplicationSupportsP2P;
+        }
+
+        @Override
+        public int[] getSupportedServicesOnCarrierRoamingNtn(int subId) {
+            return new int[]{3, 5};
+        }
+
+
         void setSatelliteProvisioned(@Nullable Boolean isProvisioned) {
-            synchronized (mSatelliteViaOemProvisionLock) {
-                mIsSatelliteViaOemProvisioned = isProvisioned;
+            synchronized (mDeviceProvisionLock) {
+                mIsDeviceProvisioned = isProvisioned;
+            }
+        }
+
+        void setIsSatelliteSupported(@Nullable Boolean isSatelliteSupported) {
+            synchronized (mIsSatelliteSupportedLock) {
+                mIsSatelliteSupported = isSatelliteSupported;
             }
         }
 
@@ -5440,6 +6337,10 @@
             evaluateESOSProfilesPrioritization();
         }
 
+        public boolean isCarrierSatelliteHigherPriorityTest(SubscriptionInfo info) {
+            return isCarrierSatelliteHigherPriority(info);
+        }
+
         public String getStringFromOverlayConfigTest(int resourceId) {
             return getStringFromOverlayConfig(resourceId);
         }
@@ -5447,5 +6348,349 @@
         public boolean isAnyWaitForSatelliteEnablingResponseTimerStarted() {
             return hasMessages(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT);
         }
+
+        public int getResultReceiverTotalCount() {
+            synchronized (mResultReceiverTotalCountLock) {
+                return mResultReceiverTotalCount;
+            }
+        }
+
+        public HashMap<String, Integer> getResultReceiverCountPerMethodMap() {
+            synchronized (mResultReceiverTotalCountLock) {
+                return mResultReceiverCountPerMethodMap;
+            }
+        }
+
+        public void setIsSatelliteAllowedState(boolean isAllowed) {
+            synchronized(mSatelliteAccessConfigLock) {
+                mSatelliteAccessAllowed = isAllowed;
+            }
+        }
+    }
+
+    @Test
+    public void testLoggingCodeForResultReceiverCount() throws Exception {
+        final String callerSC =  "SC:ResultReceiver";
+        final String callerSAC =  "SAC:ResultReceiver";
+
+        doReturn(false).when(mFeatureFlags).carrierRoamingNbIotNtn();
+
+        mSatelliteControllerUT.incrementResultReceiverCount(callerSC);
+        assertEquals(0, mSatelliteControllerUT.getResultReceiverTotalCount());
+        mSatelliteControllerUT.decrementResultReceiverCount(callerSC);
+        assertEquals(0, mSatelliteControllerUT.getResultReceiverTotalCount());
+
+        doReturn(true).when(mFeatureFlags).carrierRoamingNbIotNtn();
+
+        mSatelliteControllerUT.incrementResultReceiverCount(callerSC);
+        assertEquals(1, mSatelliteControllerUT.getResultReceiverTotalCount());
+        assertEquals(1, mSatelliteControllerUT.getResultReceiverCountPerMethodMap().size());
+        assertEquals(1, (int) Optional.ofNullable(mSatelliteControllerUT
+                .getResultReceiverCountPerMethodMap().get(callerSC)).orElse(0));
+        assertEquals(0, (int) Optional.ofNullable(mSatelliteControllerUT
+                .getResultReceiverCountPerMethodMap().get(callerSAC)).orElse(0));
+
+        mSatelliteControllerUT.incrementResultReceiverCount(callerSC);
+        assertEquals(2, mSatelliteControllerUT.getResultReceiverTotalCount());
+        assertEquals(1, mSatelliteControllerUT.getResultReceiverCountPerMethodMap().size());
+        assertEquals(2, (int) Optional.ofNullable(mSatelliteControllerUT
+                .getResultReceiverCountPerMethodMap().get(callerSC)).orElse(0));
+        assertEquals(0, (int) Optional.ofNullable(mSatelliteControllerUT
+                .getResultReceiverCountPerMethodMap().get(callerSAC)).orElse(0));
+
+        mSatelliteControllerUT.incrementResultReceiverCount(callerSAC);
+        assertEquals(3, mSatelliteControllerUT.getResultReceiverTotalCount());
+        assertEquals(2, mSatelliteControllerUT.getResultReceiverCountPerMethodMap().size());
+        assertEquals(2, (int) Optional.ofNullable(mSatelliteControllerUT
+                .getResultReceiverCountPerMethodMap().get(callerSC)).orElse(0));
+        assertEquals(1, (int) Optional.ofNullable(mSatelliteControllerUT
+                .getResultReceiverCountPerMethodMap().get(callerSAC)).orElse(0));
+
+        mSatelliteControllerUT.decrementResultReceiverCount(callerSC);
+        assertEquals(2, mSatelliteControllerUT.getResultReceiverTotalCount());
+        assertEquals(2, mSatelliteControllerUT.getResultReceiverCountPerMethodMap().size());
+        assertEquals(1, (int) Optional.ofNullable(mSatelliteControllerUT
+                .getResultReceiverCountPerMethodMap().get(callerSC)).orElse(0));
+        assertEquals(1, (int) Optional.ofNullable(mSatelliteControllerUT
+                .getResultReceiverCountPerMethodMap().get(callerSAC)).orElse(0));
+
+        mSatelliteControllerUT.decrementResultReceiverCount(callerSC);
+        assertEquals(1, mSatelliteControllerUT.getResultReceiverTotalCount());
+        assertEquals(2, mSatelliteControllerUT.getResultReceiverCountPerMethodMap().size());
+        assertEquals(0, (int) Optional.ofNullable(mSatelliteControllerUT
+                .getResultReceiverCountPerMethodMap().get(callerSC)).orElse(0));
+        assertEquals(1, (int) Optional.ofNullable(mSatelliteControllerUT
+                .getResultReceiverCountPerMethodMap().get(callerSAC)).orElse(0));
+
+        mSatelliteControllerUT.decrementResultReceiverCount(callerSAC);
+        assertEquals(0, mSatelliteControllerUT.getResultReceiverTotalCount());
+        assertEquals(2, mSatelliteControllerUT.getResultReceiverCountPerMethodMap().size());
+        assertEquals(0, (int) Optional.ofNullable(mSatelliteControllerUT
+                .getResultReceiverCountPerMethodMap().get(callerSC)).orElse(0));
+        assertEquals(0, (int) Optional.ofNullable(mSatelliteControllerUT
+                .getResultReceiverCountPerMethodMap().get(callerSAC)).orElse(0));
+    }
+
+    @Test
+    public void testSetNtnSmsSupportedByMessagesApp() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mSatelliteControllerUT.setNtnSmsSupportedByMessagesApp(true);
+        assertTrue(mSharedPreferences.getBoolean(
+                SatelliteController.NTN_SMS_SUPPORTED_BY_MESSAGES_APP_KEY, false));
+    }
+
+    private static class TestSubscriptionManager {
+        public Map<Integer, Boolean> mSatelliteProvisionedForNonIpDatagram = new HashMap<>();
+
+        public void resetProvisionMapForNonIpDatagram() {
+            mSatelliteProvisionedForNonIpDatagram.clear();
+        }
+
+        public void setIsSatelliteProvisionedForNonIpDatagram(int subId, boolean provisioned) {
+            mSatelliteProvisionedForNonIpDatagram.put(subId, provisioned);
+        }
+
+        public boolean isSatelliteProvisionedForNonIpDatagram(int subId) {
+            Boolean isProvisioned = mSatelliteProvisionedForNonIpDatagram.get(subId);
+            return isProvisioned != null ? isProvisioned : false;
+        }
+    }
+
+    @Test
+    public void testGetSatelliteDataPlanForPlmn_WithEntitlement() throws Exception {
+        logd("testGetSatelliteDataPlanForPlmn_WithEntitlement");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        replaceInstance(SatelliteController.class, "mMergedPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        List<String> overlayConfigPlmnList = new ArrayList<>();
+        replaceInstance(SatelliteController.class, "mSatellitePlmnListFromOverlayConfig",
+                mSatelliteControllerUT, overlayConfigPlmnList);
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+
+        List<String> entitlementPlmnList =
+                Arrays.stream(new String[]{"00101", "00102", "00103", "00104"})
+                        .toList();
+        List<String> barredPlmnList = new ArrayList<>();
+        Map<String, Integer> dataPlanListMap = Map.of(
+                "00101", SATELLITE_DATA_PLAN_METERED,
+                "00103", SATELLITE_DATA_PLAN_UNMETERED);
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
+                entitlementPlmnList, barredPlmnList, dataPlanListMap, new HashMap<>(),
+                new HashMap<>(), new HashMap<>(), mIIntegerConsumer);
+
+        int dataPlanForPlmn;
+        dataPlanForPlmn = mSatelliteControllerUT.getSatelliteDataPlanForPlmn(SUB_ID, "00101");
+        assertEquals(SATELLITE_DATA_PLAN_METERED, dataPlanForPlmn);
+
+        dataPlanForPlmn = mSatelliteControllerUT.getSatelliteDataPlanForPlmn(SUB_ID, "00103");
+        assertEquals(SATELLITE_DATA_PLAN_UNMETERED, dataPlanForPlmn);
+    }
+
+    @Test
+    public void testGetSatelliteDataPlanForPlmn_WithoutEntitlement() throws Exception {
+        logd("testGetSatelliteDataPlanForPlmn_WithoutEntitlement");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        replaceInstance(SatelliteController.class, "mMergedPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        List<String> overlayConfigPlmnList = new ArrayList<>();
+        replaceInstance(SatelliteController.class, "mSatellitePlmnListFromOverlayConfig",
+                mSatelliteControllerUT, overlayConfigPlmnList);
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+
+        List<String> entitlementPlmnList =
+                Arrays.stream(new String[]{"00101", "00102", "00103", "00104"})
+                        .toList();
+        List<String> barredPlmnList = new ArrayList<>();
+        Map<String, Integer> dataPlanListMap = new HashMap<>();
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
+                entitlementPlmnList, barredPlmnList, dataPlanListMap, new HashMap<>(),
+                new HashMap<>(), new HashMap<>(), mIIntegerConsumer);
+
+        int dataPlanForPlmn = mSatelliteControllerUT.getSatelliteDataPlanForPlmn(SUB_ID, "00101");
+        assertEquals(SATELLITE_DATA_PLAN_METERED, dataPlanForPlmn);
+    }
+
+    @Test
+    public void TestGetSupportedSatelliteServicesForPlmn_WithEntitlement() throws Exception {
+        logd("TestGetSupportedSatelliteServicesForPlmn_WithEntitlement");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        replaceInstance(SatelliteController.class, "mMergedPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        List<String> overlayConfigPlmnList = new ArrayList<>();
+        replaceInstance(SatelliteController.class, "mSatellitePlmnListFromOverlayConfig",
+                mSatelliteControllerUT, overlayConfigPlmnList);
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+
+        List<String> entitlementPlmnList =
+                Arrays.stream(new String[]{"00101", "00102", "00103", "00104"})
+                        .toList();
+        List<String> barredPlmnList = new ArrayList<>();
+        Map<String, List<Integer>> serviceTypeListMap = Map.of(
+                "00101", List.of(SERVICE_TYPE_DATA, SERVICE_TYPE_SMS),
+                "00102", List.of(SERVICE_TYPE_VOICE, SERVICE_TYPE_SMS),
+                "00103", List.of(SERVICE_TYPE_DATA, SERVICE_TYPE_VOICE, SERVICE_TYPE_SMS));
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
+                entitlementPlmnList, barredPlmnList, new HashMap<>(), serviceTypeListMap,
+                new HashMap<>(), new HashMap<>(), mIIntegerConsumer);
+
+        List<Integer> allowedServiceForPlmn;
+        allowedServiceForPlmn = mSatelliteControllerUT
+                .getSupportedSatelliteServicesForPlmn(SUB_ID, "00101");
+        assertEquals(List.of(SERVICE_TYPE_DATA, SERVICE_TYPE_SMS), allowedServiceForPlmn);
+
+        allowedServiceForPlmn = mSatelliteControllerUT
+                .getSupportedSatelliteServicesForPlmn(SUB_ID, "00102");
+        assertEquals(List.of(SERVICE_TYPE_VOICE, SERVICE_TYPE_SMS), allowedServiceForPlmn);
+
+        allowedServiceForPlmn = mSatelliteControllerUT
+                .getSupportedSatelliteServicesForPlmn(SUB_ID, "00103");
+        assertEquals(List.of(SERVICE_TYPE_DATA, SERVICE_TYPE_VOICE, SERVICE_TYPE_SMS),
+                allowedServiceForPlmn);
+    }
+
+    @Test
+    public void TestGetSupportedSatelliteServicesForPlmn_WithoutEntitlement() throws Exception {
+        logd("TestGetSupportedSatelliteServicesForPlmn_WithoutAllowedServices");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        replaceInstance(SatelliteController.class, "mMergedPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        List<String> overlayConfigPlmnList = new ArrayList<>();
+        replaceInstance(SatelliteController.class, "mSatellitePlmnListFromOverlayConfig",
+                mSatelliteControllerUT, overlayConfigPlmnList);
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+
+        List<String> entitlementPlmnList =
+                Arrays.stream(new String[]{"00101", "00102", "00103", "00104"})
+                        .toList();
+        List<String> barredPlmnList = new ArrayList<>();
+        Map<String, Integer> dataPlanListMap =  new HashMap<>();
+        Map<String, List<Integer>> allowedServiceListMap = new HashMap<>();
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
+                entitlementPlmnList, barredPlmnList, dataPlanListMap, allowedServiceListMap,
+                new HashMap<>(), new HashMap<>(), mIIntegerConsumer);
+
+        // Verify whether the carrier config plmn list is returned with conditions below
+        // the config data plmn list : empty
+        // the carrier config plmn list : exist with services {{2}}
+        setConfigData(new ArrayList<>());
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+        PersistableBundle carrierSupportedSatelliteServicesPerProvider =
+                new PersistableBundle();
+        List<String> carrierConfigPlmnList = List.of("00101");
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                carrierConfigPlmnList.get(0), new int[]{2});
+        mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager
+                        .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                carrierSupportedSatelliteServicesPerProvider);
+        invokeCarrierConfigChanged();
+
+        List<Integer> servicesPerPlmn;
+        servicesPerPlmn = mSatelliteControllerUT.getSupportedSatelliteServicesForPlmn(
+                SUB_ID, "00101");
+        assertEquals(Arrays.asList(2).stream().sorted().toList(),
+                servicesPerPlmn.stream().sorted().toList());
+    }
+
+    @Test
+    public void testGetSupportedSatelliteDataModeForPlmn_WithEntitlement() throws Exception {
+        logd("testGetSupportedSatelliteDataModeForPlmn_WithEntitlement");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        replaceInstance(SatelliteController.class, "mMergedPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        List<String> overlayConfigPlmnList = new ArrayList<>();
+        replaceInstance(SatelliteController.class, "mSatellitePlmnListFromOverlayConfig",
+                mSatelliteControllerUT, overlayConfigPlmnList);
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+
+        List<String> entitlementPlmnList =
+                Arrays.stream(new String[]{"00101", "00102", "00103", "00104"})
+                        .toList();
+        List<String> barredPlmnList = new ArrayList<>();
+        Map<String, Integer> dataServicePolicyMap = Map.of(
+                "00101", SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED,
+                "00102", SATELLITE_DATA_SUPPORT_ALL
+        );
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
+                entitlementPlmnList, barredPlmnList, new HashMap<>(), new HashMap<>(),
+                dataServicePolicyMap, new HashMap<>(), mIIntegerConsumer);
+
+        int dataSupportModeForPlmn;
+        dataSupportModeForPlmn = mSatelliteControllerUT
+                .getSatelliteDataServicePolicyForPlmn(SUB_ID, "00101");
+        assertEquals(SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED, dataSupportModeForPlmn);
+
+        dataSupportModeForPlmn = mSatelliteControllerUT
+                .getSatelliteDataServicePolicyForPlmn(SUB_ID, "00102");
+        assertEquals(SATELLITE_DATA_SUPPORT_ALL, dataSupportModeForPlmn);
+
+    }
+
+    @Test
+    public void testGetSupportedSatelliteDataModeForPlmn_WithoutEntitlement() throws Exception {
+        logd("testGetSupportedSatelliteDataModeForPlmn_WithoutEntitlement");
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
+        replaceInstance(SatelliteController.class, "mMergedPlmnListPerCarrier",
+                mSatelliteControllerUT, new SparseArray<>());
+        List<String> overlayConfigPlmnList = new ArrayList<>();
+        replaceInstance(SatelliteController.class, "mSatellitePlmnListFromOverlayConfig",
+                mSatelliteControllerUT, overlayConfigPlmnList);
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
+
+        List<String> entitlementPlmnList =
+                Arrays.stream(new String[]{"00101", "00102", "00103", "00104"})
+                        .toList();
+        List<String> barredPlmnList = new ArrayList<>();
+        Map<String, Integer> dataServicePolicyMap = new HashMap<>();
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
+                entitlementPlmnList, barredPlmnList, new HashMap<>(), new HashMap<>(),
+                dataServicePolicyMap, new HashMap<>(), mIIntegerConsumer);
+
+        mCarrierConfigBundle.putInt(
+                CarrierConfigManager.KEY_SATELLITE_DATA_SUPPORT_MODE_INT,
+                SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED);
+        int dataSupportModeForPlmn = mSatelliteControllerUT
+                .getSatelliteDataServicePolicyForPlmn(SUB_ID, "00101");
+        assertEquals(SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED, dataSupportModeForPlmn);
+    }
+
+    @Test
+    public void testEvaluateCarrierRoamingNtnEligibilityChange_inSatelliteMode() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+
+        mSatelliteControllerUT.isSatelliteEnabledOrBeingEnabled = true;
+        mSatelliteControllerUT.setSatellitePhone(1);
+        mSatelliteControllerUT.setSelectedSatelliteSubId(SUB_ID);
+        mSatelliteControllerUT.isSatelliteProvisioned = true;
+        mSatelliteControllerUT.isSatelliteAllowedCallback = null;
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.setIsSatelliteAllowedState(true);
+        sendCmdEvaluateCarrierRoamingNtnEligibilityChange();
+        processAllMessages();
+        verify(mPhone, times(0)).notifyCarrierRoamingNtnEligibleStateChanged(anyBoolean());
     }
 }
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 230a9b1..a5fd30d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
@@ -29,6 +29,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -39,16 +43,19 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.hardware.devicestate.DeviceState;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Looper;
 import android.os.OutcomeReceiver;
 import android.os.RemoteException;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.telecom.Connection;
 import android.telecom.TelecomManager;
 import android.telephony.BinderCacheManager;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.RegistrationManager;
@@ -58,6 +65,7 @@
 import android.testing.TestableLooper;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.ims.ImsException;
 import com.android.ims.ImsManager;
@@ -67,6 +75,8 @@
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 
 import org.junit.After;
 import org.junit.Before;
@@ -93,9 +103,11 @@
 public class SatelliteSOSMessageRecommenderTest extends TelephonyTest {
     private static final String TAG = "SatelliteSOSMessageRecommenderTest";
     private static final int TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS = 500;
+    private static final int TEST_EMERGENCY_CALL_TO_T911_MSG_HYSTERESIS_TIMEOUT_MILLIS = 1000;
     private static final int PHONE_ID = 0;
     private static final int PHONE_ID2 = 1;
     private static final int SUB_ID = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+    private static final int SUB_ID1 = 1;
     private static final String CALL_ID = "CALL_ID";
     private static final String WRONG_CALL_ID = "WRONG_CALL_ID";
     private static final String DEFAULT_SATELLITE_MESSAGING_PACKAGE = "android.com.google.default";
@@ -103,6 +115,12 @@
             "android.com.google.default.SmsMmsApp";
     private static final String DEFAULT_HANDOVER_INTENT_ACTION =
             "android.com.vendor.action.EMERGENCY_MESSAGING";
+    private static final String DEFAULT_SOS_HANDOVER_APP =
+            "android.com.vendor.message;android.com.vendor.message.SosHandoverApp";
+    private static final String DEFAULT_SATELLITE_SOS_HANDOVER_PACKAGE =
+        "android.com.vendor.message";
+    private static final String DEFAULT_SATELLITE_SOS_HANDOVER_CLASS =
+            "android.com.vendor.message.SosHandoverApp";
     private static final String DEFAULT_T911_HANDOVER_INTENT_ACTION = Intent.ACTION_SENDTO;
     private TestSatelliteController mTestSatelliteController;
     private TestImsManager mTestImsManager;
@@ -118,6 +136,9 @@
     private Uri mTestConnectionAddress = Uri.parse("tel:1234");
     private TestSOSMessageRecommender mTestSOSMessageRecommender;
     private ServiceState mServiceState2;
+    @Mock
+    private SatelliteStats mMockSatelliteStats;
+    @Mock private SubscriptionManagerService mMockSubscriptionManagerService;
 
     @Before
     public void setUp() throws Exception {
@@ -132,6 +153,8 @@
         when(mResources.getInteger(
                 R.integer.config_emergency_call_wait_for_connection_timeout_millis))
                 .thenReturn(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
+        when(mResources.getString(R.string.config_oem_enabled_satellite_sos_handover_app))
+                .thenReturn(DEFAULT_SOS_HANDOVER_APP);
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
         when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
         mTestSatelliteController = new TestSatelliteController(mContext,
@@ -145,14 +168,31 @@
         mServiceState2 = Mockito.mock(ServiceState.class);
         when(mPhone.getServiceState()).thenReturn(mServiceState);
         when(mPhone.getPhoneId()).thenReturn(PHONE_ID);
+        when(mPhone.getSignalStrengthController()).thenReturn(mSignalStrengthController);
         when(mPhone2.getServiceState()).thenReturn(mServiceState2);
         when(mPhone2.getPhoneId()).thenReturn(PHONE_ID2);
+        when(mPhone2.getSignalStrengthController()).thenReturn(mSignalStrengthController);
         mTestSOSMessageRecommender = new TestSOSMessageRecommender(mContext, Looper.myLooper(),
                 mTestSatelliteController, mTestImsManager);
         when(mServiceState.getState()).thenReturn(STATE_OUT_OF_SERVICE);
         when(mServiceState2.getState()).thenReturn(STATE_OUT_OF_SERVICE);
         when(mPhone.isImsRegistered()).thenReturn(false);
         when(mPhone2.isImsRegistered()).thenReturn(false);
+        replaceInstance(SatelliteStats.class, "sInstance", null,
+                mMockSatelliteStats);
+        replaceInstance(SubscriptionManagerService.class, "sInstance", null,
+                mMockSubscriptionManagerService);
+        doNothing().when(mMockSatelliteStats).onSatelliteSosMessageRecommender(
+                any(SatelliteStats.SatelliteSosMessageRecommenderParams.class));
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(
+            false, -1);
+        mTestSOSMessageRecommender.setSatelliteConnectedViaCarrierWithinHysteresisTime(
+            false, -1);
+        mTestSatelliteController.selectedSatelliteSubId = SUB_ID1;
+        SubscriptionInfo subscriptionInfo = new SubscriptionInfo.Builder()
+                .setId(SUB_ID1).setOnlyNonTerrestrialNetwork(true).build();
+        when(mMockSubscriptionManagerService.getSubscriptionInfo(eq(SUB_ID1)))
+            .thenReturn(subscriptionInfo);
     }
 
     @After
@@ -162,9 +202,16 @@
 
     @Test
     public void testTimeoutBeforeEmergencyCallEnd_T911() {
-        testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911,
-                DEFAULT_SATELLITE_MESSAGING_PACKAGE, DEFAULT_SATELLITE_MESSAGING_CLASS,
-                DEFAULT_T911_HANDOVER_INTENT_ACTION);
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(
+            true, SUB_ID1);
+        testTimeoutBeforeEmergencyCallEnd(
+            TEST_EMERGENCY_CALL_TO_T911_MSG_HYSTERESIS_TIMEOUT_MILLIS,
+            EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911,
+            DEFAULT_SATELLITE_MESSAGING_PACKAGE,
+            DEFAULT_SATELLITE_MESSAGING_CLASS,
+            DEFAULT_T911_HANDOVER_INTENT_ACTION);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -173,10 +220,14 @@
                 "android.com.vendor.message;android.com.vendor.message.SmsApp";
         when(mResources.getString(R.string.config_oem_enabled_satellite_sos_handover_app))
                 .thenReturn(satelliteHandoverApp);
-        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
-        testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS,
-                "android.com.vendor.message", "android.com.vendor.message.SmsApp",
-                DEFAULT_HANDOVER_INTENT_ACTION);
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false, -1);
+        testTimeoutBeforeEmergencyCallEnd(
+            TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS,
+            EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS,
+            "android.com.vendor.message", "android.com.vendor.message.SmsApp",
+            DEFAULT_HANDOVER_INTENT_ACTION);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -185,18 +236,26 @@
                 "android.com.vendor.message;android.com.vendor.message.SmsApp;abc";
         when(mResources.getString(R.string.config_oem_enabled_satellite_sos_handover_app))
                 .thenReturn(satelliteHandoverApp);
-        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
-        testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS, "", "",
-                DEFAULT_HANDOVER_INTENT_ACTION);
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false, -1);
+        testTimeoutBeforeEmergencyCallEnd(
+            TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS,
+            EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS, "", "",
+            DEFAULT_HANDOVER_INTENT_ACTION);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testTimeoutBeforeEmergencyCallEnd_SOS_WithoutHandoverAppConfigured() {
         when(mResources.getString(R.string.config_oem_enabled_satellite_sos_handover_app))
                 .thenReturn("");
-        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
-        testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS, "", "",
-                DEFAULT_HANDOVER_INTENT_ACTION);
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false, -1);
+        testTimeoutBeforeEmergencyCallEnd(
+            TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS,
+            EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS, "", "",
+            DEFAULT_HANDOVER_INTENT_ACTION);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -208,9 +267,10 @@
         processAllMessages();
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
     }
 
-    private void testTimeoutBeforeEmergencyCallEnd(int expectedHandoverType,
+    private void testTimeoutBeforeEmergencyCallEnd(int timeoutMillis, int expectedHandoverType,
             String expectedPackageName, String expectedClassName, String expectedAction) {
         mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
@@ -228,7 +288,7 @@
 
         // Wait for the timeout to expires
         mTestSOSMessageRecommender.isSatelliteAllowedCallback.onResult(true);
-        moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
+        moveTimeForward(timeoutMillis);
         processAllMessages();
         if (TextUtils.isEmpty(expectedPackageName) || TextUtils.isEmpty(expectedClassName)) {
             assertTrue(mTestConnection.isEventWithoutLaunchIntentSent(
@@ -239,12 +299,14 @@
         }
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testTimeoutBeforeEmergencyCallEnd_EventDisplayEmergencyMessageNotSent() {
-        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
-        mTestSatelliteController.setIsSatelliteViaOemProvisioned(false);
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false, -1);
+        mTestSatelliteController.setDeviceProvisioned(false);
         mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
@@ -266,12 +328,14 @@
         assertFalse(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE));
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertFalse(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testTimeoutBeforeEmergencyCallEnd_T911_FromNotConnectedToConnected() {
         mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
-        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false, -1);
         mTestSatelliteController.isOemEnabledSatelliteSupported = false;
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
@@ -286,7 +350,8 @@
         processAllMessages();
         assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
 
-        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(true);
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(
+            true, SUB_ID1);
         // Wait for the timeout to expires
         moveTimeForward(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
         processAllMessages();
@@ -302,11 +367,15 @@
     @Test
     public void testStopTrackingCallBeforeTimeout_ConnectionActive() {
         testStopTrackingCallBeforeTimeout(Connection.STATE_ACTIVE);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertFalse(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testStopTrackingCallBeforeTimeout_ConnectionDisconnected() {
         testStopTrackingCallBeforeTimeout(Connection.STATE_DISCONNECTED);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertFalse(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -332,15 +401,19 @@
         processAllMessages();
 
         assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
-                EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911, DEFAULT_SATELLITE_MESSAGING_PACKAGE,
-                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_T911_HANDOVER_INTENT_ACTION));
+                EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS,
+                DEFAULT_SATELLITE_SOS_HANDOVER_PACKAGE,
+                DEFAULT_SATELLITE_SOS_HANDOVER_CLASS, DEFAULT_HANDOVER_INTENT_ACTION));
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testSatelliteProvisionStateChangedBeforeTimeout() {
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false, -1);
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
 
@@ -357,6 +430,9 @@
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertFalse(mTestSOSMessageRecommender.isDialerNotified());
+        reset(mMockSatelliteStats);
 
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
@@ -381,12 +457,15 @@
         processAllMessages();
 
         assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
-                EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911, DEFAULT_SATELLITE_MESSAGING_PACKAGE,
-                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_T911_HANDOVER_INTENT_ACTION));
+                EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS,
+                DEFAULT_SATELLITE_SOS_HANDOVER_PACKAGE,
+                DEFAULT_SATELLITE_SOS_HANDOVER_CLASS, DEFAULT_HANDOVER_INTENT_ACTION));
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertUnregisterForStateChangedEventsTriggered(mPhone, 2, 2);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 2, 2);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -420,36 +499,47 @@
         processAllMessages();
 
         assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
-                EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911, DEFAULT_SATELLITE_MESSAGING_PACKAGE,
-                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_T911_HANDOVER_INTENT_ACTION));
+                EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS,
+                DEFAULT_SATELLITE_SOS_HANDOVER_PACKAGE,
+                DEFAULT_SATELLITE_SOS_HANDOVER_CLASS, DEFAULT_HANDOVER_INTENT_ACTION));
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testCellularServiceStateChangedBeforeTimeout_InServiceToOutOfService() {
         testCellularServiceStateChangedBeforeTimeout(
                 ServiceState.STATE_IN_SERVICE, STATE_OUT_OF_SERVICE);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testCellularServiceStateChangedBeforeTimeout_InServiceToPowerOff() {
         testCellularServiceStateChangedBeforeTimeout(
                 ServiceState.STATE_IN_SERVICE, ServiceState.STATE_POWER_OFF);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testCellularServiceStateChangedBeforeTimeout_EmergencyOnlyToOutOfService() {
         testCellularServiceStateChangedBeforeTimeout(
                 ServiceState.STATE_EMERGENCY_ONLY, STATE_OUT_OF_SERVICE);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testCellularServiceStateChangedBeforeTimeout_EmergencyOnlyToPowerOff() {
         testCellularServiceStateChangedBeforeTimeout(
                 ServiceState.STATE_EMERGENCY_ONLY, ServiceState.STATE_POWER_OFF);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -471,6 +561,8 @@
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertFalse(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -498,38 +590,43 @@
         assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertFalse(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testOnEmergencyCallStarted() {
-        SatelliteController satelliteController = new SatelliteController(
-                mContext, Looper.myLooper(), mFeatureFlags);
+        SatelliteController satelliteController = new MinimalSatelliteControllerWrapper(mContext,
+                Looper.myLooper(), mFeatureFlags);
         TestSOSMessageRecommender testSOSMessageRecommender = new TestSOSMessageRecommender(
                 mContext,
                 Looper.myLooper(),
                 satelliteController, mTestImsManager);
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(
+            true, SUB_ID1);
         testSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
 
         assertFalse(testSOSMessageRecommender.isTimerStarted());
         assertEquals(0, testSOSMessageRecommender.getCountOfTimerStarted());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
     }
 
     @Test
-    public void testIsSatelliteViaOemAvailable() {
+    public void testIsDeviceProvisioned() {
         Boolean originalIsSatelliteViaOemProvisioned =
-                mTestSatelliteController.mIsSatelliteViaOemProvisioned;
+                mTestSatelliteController.mIsDeviceProvisionedForTest;
 
-        mTestSatelliteController.mIsSatelliteViaOemProvisioned = null;
-        assertFalse(mTestSOSMessageRecommender.isSatelliteViaOemAvailable());
+        mTestSatelliteController.mIsDeviceProvisionedForTest = null;
+        assertFalse(mTestSOSMessageRecommender.isDeviceProvisioned());
 
-        mTestSatelliteController.mIsSatelliteViaOemProvisioned = true;
-        assertTrue(mTestSOSMessageRecommender.isSatelliteViaOemAvailable());
+        mTestSatelliteController.mIsDeviceProvisionedForTest = true;
+        assertTrue(mTestSOSMessageRecommender.isDeviceProvisioned());
 
-        mTestSatelliteController.mIsSatelliteViaOemProvisioned = false;
-        assertFalse(mTestSOSMessageRecommender.isSatelliteViaOemAvailable());
+        mTestSatelliteController.mIsDeviceProvisionedForTest = false;
+        assertFalse(mTestSOSMessageRecommender.isDeviceProvisioned());
 
-        mTestSatelliteController.mIsSatelliteViaOemProvisioned =
+        mTestSatelliteController.mIsDeviceProvisionedForTest =
                 originalIsSatelliteViaOemProvisioned;
     }
 
@@ -548,39 +645,59 @@
         processAllMessages();
         assertEquals(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS,
                 mTestSOSMessageRecommender.getTimeOutMillis());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
 
         // Both OEM and carrier support satellite, but device is not connected to carrier satellite
         // within hysteresis time. Thus, OEM timer will be used.
-        long carrierTimeoutMillis = 1000;
         mTestSatelliteController.isSatelliteEmergencyMessagingSupportedViaCarrier = true;
-        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
-        mTestSatelliteController.carrierEmergencyCallWaitForConnectionTimeoutMillis =
-                carrierTimeoutMillis;
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false, -1);
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
         assertEquals(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS,
                 mTestSOSMessageRecommender.getTimeOutMillis());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
 
         // Both OEM and carrier support satellite, and device is connected to carrier satellite
         // within hysteresis time. Thus, carrier timer will be used.
+        int carrierTimeoutMillis = 1000;
         mTestSatelliteController.isSatelliteEmergencyMessagingSupportedViaCarrier = true;
-        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(true);
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(
+            true, SUB_ID1);
         mTestSatelliteController.carrierEmergencyCallWaitForConnectionTimeoutMillis =
                 carrierTimeoutMillis;
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
         assertEquals(carrierTimeoutMillis, mTestSOSMessageRecommender.getTimeOutMillis());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
+
+        // Both OEM and carrier support satellite, device is not connected to carrier satellite
+        // within hysteresis time, but selected satellite subId is not NTN only. Thus, carrier
+        // timer will be used.
+        carrierTimeoutMillis = 2000;
+        SubscriptionInfo subscriptionInfo = new SubscriptionInfo.Builder()
+                .setId(SUB_ID1).setOnlyNonTerrestrialNetwork(false).build();
+        when(mMockSubscriptionManagerService.getSubscriptionInfo(eq(SUB_ID1)))
+            .thenReturn(subscriptionInfo);
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false, -1);
+        mTestSatelliteController.carrierEmergencyCallWaitForConnectionTimeoutMillis =
+                carrierTimeoutMillis;
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
+        processAllMessages();
+        assertEquals(carrierTimeoutMillis, mTestSOSMessageRecommender.getTimeOutMillis());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
     }
 
     @Test
     public void testGetEmergencyCallToSatelliteHandoverType_SatelliteViaCarrierAndOemAvailable() {
         mSetFlagsRule.enableFlags(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN);
 
-        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(true);
-        mTestSatelliteController.mIsSatelliteViaOemProvisioned = true;
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(
+            true, SUB_ID1);
+        mTestSatelliteController.mIsDeviceProvisionedForTest = true;
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         assertEquals(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911,
                 mTestSOSMessageRecommender.getEmergencyCallToSatelliteHandoverType());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
 
         mSetFlagsRule.disableFlags(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN);
     }
@@ -589,11 +706,13 @@
     public void testGetEmergencyCallToSatelliteHandoverType_OnlySatelliteViaCarrierAvailable() {
         mSetFlagsRule.enableFlags(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN);
 
-        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(true);
-        mTestSatelliteController.mIsSatelliteViaOemProvisioned = false;
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(
+            true, SUB_ID1);
+        mTestSatelliteController.mIsDeviceProvisionedForTest = false;
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         assertEquals(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911,
                 mTestSOSMessageRecommender.getEmergencyCallToSatelliteHandoverType());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
 
         mSetFlagsRule.disableFlags(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN);
     }
@@ -602,17 +721,18 @@
     public void testGetEmergencyCallToSatelliteHandoverType_OemAndCarrierNotAvailable() {
         mSetFlagsRule.enableFlags(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN);
 
-        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
-        mTestSatelliteController.mIsSatelliteViaOemProvisioned = true;
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false, -1);
+        mTestSatelliteController.mIsDeviceProvisionedForTest = true;
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         assertEquals(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS,
                 mTestSOSMessageRecommender.getEmergencyCallToSatelliteHandoverType());
 
-        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
-        mTestSatelliteController.mIsSatelliteViaOemProvisioned = false;
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false, -1);
+        mTestSatelliteController.mIsDeviceProvisionedForTest = false;
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         assertEquals(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS,
                 mTestSOSMessageRecommender.getEmergencyCallToSatelliteHandoverType());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
 
         mSetFlagsRule.disableFlags(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN);
     }
@@ -683,8 +803,9 @@
         processAllMessages();
 
         assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
-                EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911, DEFAULT_SATELLITE_MESSAGING_PACKAGE,
-                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_T911_HANDOVER_INTENT_ACTION));
+                EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS,
+                DEFAULT_SATELLITE_SOS_HANDOVER_PACKAGE,
+                DEFAULT_SATELLITE_SOS_HANDOVER_CLASS, DEFAULT_HANDOVER_INTENT_ACTION));
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
@@ -713,13 +834,20 @@
                 mProvisionStateChangedCallbacks;
         private int mRegisterForSatelliteProvisionStateChangedCalls = 0;
         private int mUnregisterForSatelliteProvisionStateChangedCalls = 0;
-        private Boolean mIsSatelliteViaOemProvisioned = true;
+        private Boolean mIsDeviceProvisionedForTest = true;
         private boolean mIsSatelliteConnectedViaCarrierWithinHysteresisTime = true;
+        private int mSubIdOfSatelliteConnectedViaCarrierWithinHysteresisTime = -1;
         public boolean isOemEnabledSatelliteSupported = true;
         public boolean isCarrierEnabledSatelliteSupported = true;
         public boolean isSatelliteEmergencyMessagingSupportedViaCarrier = true;
-        public long carrierEmergencyCallWaitForConnectionTimeoutMillis =
-                TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS;
+        public int carrierEmergencyCallWaitForConnectionTimeoutMillis =
+                TEST_EMERGENCY_CALL_TO_T911_MSG_HYSTERESIS_TIMEOUT_MILLIS;
+        public int overrideEmergencyCallToSatelliteHandoverType =
+            SatelliteController.INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE;
+        public boolean isSatelliteEsosSupported = false;
+        public int carrierRoamingNtnEmergencyCallToSatelliteHandoverType =
+            EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911;
+        public int selectedSatelliteSubId = -1;
 
         /**
          * Create a SatelliteController to act as a backend service of
@@ -734,8 +862,8 @@
         }
 
         @Override
-        public Boolean isSatelliteViaOemProvisioned() {
-            return mIsSatelliteViaOemProvisioned;
+        public Boolean isDeviceProvisioned() {
+            return mIsDeviceProvisionedForTest;
         }
 
         @Override
@@ -749,6 +877,11 @@
         }
 
         @Override
+        protected void registerForSatelliteCommunicationAllowedStateChanged() {
+            logd("registerForSatelliteCommunicationAllowedStateChanged");
+        }
+
+        @Override
         @SatelliteManager.SatelliteResult public int registerForSatelliteProvisionStateChanged(
                 @NonNull ISatelliteProvisionStateCallback callback) {
             mRegisterForSatelliteProvisionStateChangedCalls++;
@@ -771,13 +904,14 @@
         }
 
         @Override
-        public boolean isSatelliteConnectedViaCarrierWithinHysteresisTime() {
-            return mIsSatelliteConnectedViaCarrierWithinHysteresisTime;
+        public Pair<Boolean,Integer> isSatelliteConnectedViaCarrierWithinHysteresisTime() {
+            return new Pair(mIsSatelliteConnectedViaCarrierWithinHysteresisTime,
+                mSubIdOfSatelliteConnectedViaCarrierWithinHysteresisTime);
         }
 
         @Override
         protected int getEnforcedEmergencyCallToSatelliteHandoverType() {
-            return INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE;
+            return overrideEmergencyCallToSatelliteHandoverType;
         }
 
         @Override
@@ -790,9 +924,36 @@
             return carrierEmergencyCallWaitForConnectionTimeoutMillis;
         }
 
+        @Override
+        public int getCarrierEmergencyCallWaitForConnectionTimeoutMillis(int subId) {
+            return carrierEmergencyCallWaitForConnectionTimeoutMillis;
+        }
+
+        @Override
+        protected List<DeviceState> getSupportedDeviceStates() {
+            return List.of(new DeviceState(new DeviceState.Configuration.Builder(0 /* identifier */,
+                    "DEFAULT" /* name */).build()));
+        }
+
+        @Override
+        public boolean isSatelliteEsosSupported(int subId) {
+            return isSatelliteEsosSupported;
+        }
+
+        @Override
+        public int getCarrierRoamingNtnEmergencyCallToSatelliteHandoverType(int subId) {
+            return carrierRoamingNtnEmergencyCallToSatelliteHandoverType;
+        }
+
+        @Override
+        public int getSelectedSatelliteSubId() {
+            return selectedSatelliteSubId;
+        }
+
         public void setSatelliteConnectedViaCarrierWithinHysteresisTime(
-                boolean connectedViaCarrier) {
+                boolean connectedViaCarrier, int subId) {
             mIsSatelliteConnectedViaCarrierWithinHysteresisTime = connectedViaCarrier;
+            mSubIdOfSatelliteConnectedViaCarrierWithinHysteresisTime = subId;
         }
 
         public int getRegisterForSatelliteProvisionStateChangedCalls() {
@@ -803,12 +964,12 @@
             return mUnregisterForSatelliteProvisionStateChangedCalls;
         }
 
-        public void setIsSatelliteViaOemProvisioned(boolean provisioned) {
-            mIsSatelliteViaOemProvisioned = provisioned;
+        public void setDeviceProvisioned(boolean provisioned) {
+            mIsDeviceProvisionedForTest = provisioned;
         }
 
         public void sendProvisionStateChangedEvent(int subId, boolean provisioned) {
-            mIsSatelliteViaOemProvisioned = provisioned;
+            mIsDeviceProvisionedForTest = provisioned;
             Set<ISatelliteProvisionStateCallback> perSubscriptionCallbacks =
                     mProvisionStateChangedCallbacks.get(SUB_ID);
             if (perSubscriptionCallbacks != null) {
@@ -823,6 +984,31 @@
         }
     }
 
+    /**
+     * Now that {@link SatelliteController} uses
+     * {@link android.hardware.devicestate.DeviceStateManager} to determine if a device is a
+     * foldable or not, we have to provide a minimal wrapper for {@link SatelliteController} for
+     * tests that want to use a non-fake {@link SatelliteController}.
+     */
+    private static class MinimalSatelliteControllerWrapper extends SatelliteController {
+
+        protected MinimalSatelliteControllerWrapper(
+                Context context, Looper looper, FeatureFlags featureFlags) {
+            super(context, looper, featureFlags);
+        }
+
+        @Override
+        protected List<DeviceState> getSupportedDeviceStates() {
+            return List.of(new DeviceState(new DeviceState.Configuration.Builder(0 /* identifier */,
+                    "DEFAULT" /* name */).build()));
+        }
+
+        @Override
+        protected void registerForSatelliteCommunicationAllowedStateChanged() {
+            logd("registerForSatelliteCommunicationAllowedStateChanged");
+        }
+    }
+
     private static class TestImsManager extends ImsManager {
 
         private final List<RegistrationManager.RegistrationCallback> mCallbacks;
@@ -896,6 +1082,9 @@
                 isSatelliteAllowedCallback = null;
         private ComponentName mSmsAppComponent = new ComponentName(
                 DEFAULT_SATELLITE_MESSAGING_PACKAGE, DEFAULT_SATELLITE_MESSAGING_CLASS);
+        private boolean mIsDialerNotified;
+        private boolean mProvisionState = true;
+        public boolean isSatelliteAllowedByReasons = true;
 
         /**
          * Create an instance of SatelliteSOSMessageRecommender.
@@ -924,6 +1113,22 @@
             isSatelliteAllowedCallback = callback;
         }
 
+        @Override
+        protected void reportESosRecommenderDecision(boolean isDialerNotified) {
+            super.reportESosRecommenderDecision(isDialerNotified);
+            mIsDialerNotified = isDialerNotified;
+        }
+
+        @Override
+        protected boolean updateAndGetProvisionState() {
+            return mProvisionState;
+        }
+
+        @Override
+        protected boolean isSatelliteAllowedByReasons() {
+            return isSatelliteAllowedByReasons;
+        }
+
         public boolean isTimerStarted() {
             return hasMessages(EVENT_TIME_OUT);
         }
@@ -939,6 +1144,16 @@
         public long getTimeOutMillis() {
             return mTimeoutMillis;
         }
+
+        public boolean isDialerNotified() {
+            return mIsDialerNotified;
+        }
+
+        public void setSatelliteConnectedViaCarrierWithinHysteresisTime(
+                boolean connectedViaCarrier, int subId) {
+            mIsSatelliteConnectedViaCarrierWithinHysteresisTime.set(connectedViaCarrier);
+            mSubIdOfSatelliteConnectedViaCarrierWithinHysteresisTime.set(subId);
+        }
     }
 
     private static class TestConnection extends Connection {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
index b4af458..54cf227 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
@@ -19,29 +19,40 @@
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT;
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_SMS;
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE;
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_UNKNOWN;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 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.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.AlarmManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.AsyncResult;
@@ -49,14 +60,18 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
 import android.telephony.satellite.ISatelliteModemStateCallback;
 import android.telephony.satellite.SatelliteManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import com.android.internal.R;
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
 
 import org.junit.After;
 import org.junit.Before;
@@ -69,6 +84,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -104,9 +120,14 @@
     @Mock private DatagramReceiver mMockDatagramReceiver;
     @Mock private DatagramDispatcher mMockDatagramDispatcher;
     @Mock private DatagramController mMockDatagramController;
+    @Mock private ServiceState mMockServiceState;
+    @Mock private SessionMetricsStats mMockSessionMetricsStats;
+    @Mock private AlarmManager mAlarmManager;
 
     @Captor ArgumentCaptor<Handler> mHandlerCaptor;
     @Captor ArgumentCaptor<Integer> mMsgCaptor;
+    @Captor ArgumentCaptor<Executor> mExecutorArgumentCaptor;
+    @Captor ArgumentCaptor<AlarmManager.OnAlarmListener> mOnAlarmListenerArgumentCaptor;
 
     @Before
     public void setUp() throws Exception {
@@ -121,16 +142,27 @@
                 mMockSatelliteController);
         replaceInstance(DatagramController.class, "sInstance", null,
                 mMockDatagramController);
+        replaceInstance(SessionMetricsStats.class, "sInstance", null,
+                mMockSessionMetricsStats);
 
         Resources resources = mContext.getResources();
         when(resources.getInteger(anyInt())).thenReturn(TEST_SATELLITE_TIMEOUT_MILLIS);
-
+        when(resources.getBoolean(
+                 R.bool.config_satellite_modem_support_concurrent_tn_scanning))
+            .thenReturn(false);
+        when(resources.getBoolean(
+                 R.bool.config_satellite_allow_tn_scanning_during_satellite_session))
+            .thenReturn(true);
         when(mFeatureFlags.satellitePersistentLogging()).thenReturn(true);
         when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(false);
         when(mMockSatelliteController.isSatelliteRoamingP2pSmSSupported(
                 anyInt())).thenReturn(false);
         when(mMockSatelliteController.isSatelliteEsosSupported(anyInt())).thenReturn(false);
         when(mMockSatelliteController.getSatellitePhone()).thenReturn(mPhone);
+        when(mMockSessionMetricsStats.addCountOfAutoExitDueToScreenOff()).thenReturn(
+                mMockSessionMetricsStats);
+        when(mMockSessionMetricsStats.addCountOfAutoExitDueToTnNetwork()).thenReturn(
+                mMockSessionMetricsStats);
         mSatelliteModemInterface = new TestSatelliteModemInterface(
                 mContext, mMockSatelliteController, Looper.myLooper(), mFeatureFlags);
         mTestSatelliteSessionController = new TestSatelliteSessionController(mContext,
@@ -142,6 +174,7 @@
                 mTestSatelliteModemStateCallback);
         assertSuccessfulModemStateChangedCallback(
                 mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        mTestSatelliteSessionController.setAlarmManager(mAlarmManager);
     }
 
     @After
@@ -195,6 +228,13 @@
     @Test
     public void testScreenOffInactivityTimer() {
         when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        // Support P2P_SMS
+        when(mMockSatelliteController.isSatelliteRoamingP2pSmSSupported(
+                anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getSupportedServicesOnCarrierRoamingNtn(anyInt()))
+                .thenReturn(new int[]{
+                        NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+                        NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY});
         doNothing().when(mDeviceStateMonitor).registerForScreenStateChanged(
                 eq(mTestSatelliteSessionController.getHandler()), anyInt(), any());
         when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(false);
@@ -202,6 +242,7 @@
         bundle.putInt(KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT,
                 SCREEN_OFF_INACTIVITY_TIMEOUT_SEC);
         when(mMockSatelliteController.getPersistableBundle(anyInt())).thenReturn(bundle);
+        when(mMockSatelliteController.isInCarrierRoamingNbIotNtn()).thenReturn(false);
 
         // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
         assertNotNull(mTestSatelliteSessionController);
@@ -209,19 +250,30 @@
 
         moveToIdleState();
 
+        // Even if the device is not in CarrierRoamingNbIotNtn
         // SatelliteSessionController should call registerForScreenStateChanged.
         verify(mDeviceStateMonitor).registerForScreenStateChanged(mHandlerCaptor.capture(),
                 mMsgCaptor.capture(), any());
 
+        when(mMockSatelliteController.isInCarrierRoamingNbIotNtn()).thenReturn(true);
+
         // Notify Screen off
         sendScreenStateChanged(mHandlerCaptor.getValue(), mMsgCaptor.getValue(), false);
         processAllMessages();
+        clearInvocations(mMockSatelliteController);
 
-        // Verify that the screen off inactivity timer is started.
-        assertTrue(mTestSatelliteSessionController.isScreenOffInActivityTimerStarted());
-
-        // Time shift to cause timeout
-        moveTimeForward(SCREEN_OFF_INACTIVITY_TIMEOUT_SEC * 1000);
+        // Verify that the screen off inactivity timer is set.
+        verify(mAlarmManager).setExact(
+                eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+                anyLong(),
+                anyString(),
+                mExecutorArgumentCaptor.capture(),
+                any(),
+                mOnAlarmListenerArgumentCaptor.capture()
+        );
+        // Notify alarm expired
+        mExecutorArgumentCaptor.getValue().execute(
+                () -> mOnAlarmListenerArgumentCaptor.getValue().onAlarm());
         processAllMessages();
 
         // Verify that SatelliteController#requestSatelliteEnabled() was called.
@@ -232,14 +284,22 @@
     @Test
     public void testScreenOffInactivityTimerStop() {
         when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        // Support P2P_SMS
+        when(mMockSatelliteController.isSatelliteRoamingP2pSmSSupported(
+                anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getSupportedServicesOnCarrierRoamingNtn(anyInt()))
+                .thenReturn(new int[]{
+                        NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+                        NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY});
         doNothing().when(mDeviceStateMonitor).registerForScreenStateChanged(
                 eq(mTestSatelliteSessionController.getHandler()), anyInt(), any());
         // Satellite enabling request is for an emergency.
-        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(true);
+        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(false);
         PersistableBundle bundle = new PersistableBundle();
         bundle.putInt(KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT,
                 SCREEN_OFF_INACTIVITY_TIMEOUT_SEC);
         when(mMockSatelliteController.getPersistableBundle(anyInt())).thenReturn(bundle);
+        when(mMockSatelliteController.isInCarrierRoamingNbIotNtn()).thenReturn(true);
 
         // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
         assertNotNull(mTestSatelliteSessionController);
@@ -247,18 +307,7 @@
 
         moveToIdleState();
 
-        // SatelliteSessionController should not call registerForScreenStateChanged.
-        verify(mDeviceStateMonitor, never()).registerForScreenStateChanged(
-                eq(mTestSatelliteSessionController.getHandler()), anyInt(), any());
-
-        moveToPowerOffState();
-
-        // Satellite enabling request is not for an emergency.
-        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(false);
-
-        moveToIdleState();
-
-        // SatelliteSessionController should call registerForScreenStateChanged.
+         // SatelliteSessionController should call registerForScreenStateChanged.
         verify(mDeviceStateMonitor).registerForScreenStateChanged(mHandlerCaptor.capture(),
                 mMsgCaptor.capture(), any());
 
@@ -266,27 +315,37 @@
         sendScreenStateChanged(mHandlerCaptor.getValue(), mMsgCaptor.getValue(), false);
         processAllMessages();
 
-        // Verify that the screen off inactivity timer is started.
-        assertTrue(mTestSatelliteSessionController.isScreenOffInActivityTimerStarted());
+        // Verify that the screen off inactivity timer is set.
+        verify(mAlarmManager).setExact(
+                eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+                anyLong(),
+                anyString(),
+                mExecutorArgumentCaptor.capture(),
+                any(),
+                mOnAlarmListenerArgumentCaptor.capture()
+        );
 
         // Notify Screen on
         sendScreenStateChanged(mHandlerCaptor.getValue(), mMsgCaptor.getValue(), true);
+
         processAllMessages();
 
-        // Verify that the screen off inactivity timer is stopped
-        assertFalse(mTestSatelliteSessionController.isScreenOffInActivityTimerStarted());
+        // Verify that the screen off inactivity timer is clear.
+        verify(mAlarmManager).cancel(eq(mOnAlarmListenerArgumentCaptor.getValue()));
     }
 
     @Test
     public void testP2pSmsInactivityTimer() {
         when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
-        doNothing().when(mDeviceStateMonitor).registerForScreenStateChanged(
-                eq(mTestSatelliteSessionController.getHandler()), anyInt(), any());
         when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
 
         when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(false);
         when(mMockSatelliteController.isSatelliteRoamingP2pSmSSupported(
                 anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getSupportedServicesOnCarrierRoamingNtn(anyInt()))
+                .thenReturn(new int[]{
+                        NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+                        NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY});
         when(mMockSatelliteController.isInCarrierRoamingNbIotNtn()).thenReturn(true);
         PersistableBundle bundle = new PersistableBundle();
         bundle.putInt(KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT,
@@ -295,23 +354,26 @@
 
         // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
         assertNotNull(mTestSatelliteSessionController);
+        mTestSatelliteSessionController.setSatelliteEnabledForNtnOnlySubscription(false);
         assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
         setupDatagramTransferringState(true);
 
         moveToNotConnectedState();
 
         // Verify that the P2P SMS inactivity timer is started.
-        assertTrue(mTestSatelliteSessionController.isCarrierRoamingNbIotInActivityTimerStarted());
+        assertTrue(mTestSatelliteSessionController.isP2pSmsInActivityTimerStarted());
 
         mTestSatelliteSessionController.setDeviceAlignedWithSatellite(true);
 
         // Verify that the P2P SMS inactivity timer is stopped.
-        assertFalse(mTestSatelliteSessionController.isCarrierRoamingNbIotInActivityTimerStarted());
+        assertFalse(mTestSatelliteSessionController.isP2pSmsInActivityTimerStarted());
+
+        moveNotConnectedToConnectedState();
 
         mTestSatelliteSessionController.setDeviceAlignedWithSatellite(false);
 
-        // Verify that the P2P SMS inactivity timer is started.
-        assertTrue(mTestSatelliteSessionController.isCarrierRoamingNbIotInActivityTimerStarted());
+        // Verify that the P2P SMS inactivity timer is started in CONNECTED state.
+        assertTrue(mTestSatelliteSessionController.isP2pSmsInActivityTimerStarted());
 
         // Time shift to cause timeout
         moveTimeForward(P2P_SMS_INACTIVITY_TIMEOUT_SEC * 1000);
@@ -325,8 +387,6 @@
     @Test
     public void testEsosInactivityTimer() {
         when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
-        doNothing().when(mDeviceStateMonitor).registerForScreenStateChanged(
-                eq(mTestSatelliteSessionController.getHandler()), anyInt(), any());
         when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
 
         when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(true);
@@ -338,23 +398,24 @@
 
         // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
         assertNotNull(mTestSatelliteSessionController);
+        mTestSatelliteSessionController.setSatelliteEnabledForNtnOnlySubscription(false);
         assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
         setupDatagramTransferringState(true);
 
         moveToNotConnectedState();
 
         // Verify that the ESOS inactivity timer is started.
-        assertTrue(mTestSatelliteSessionController.isCarrierRoamingNbIotInActivityTimerStarted());
+        assertTrue(mTestSatelliteSessionController.isEsosInActivityTimerStarted());
 
         mTestSatelliteSessionController.setDeviceAlignedWithSatellite(true);
 
         // Verify that the ESOS inactivity timer is stopped.
-        assertFalse(mTestSatelliteSessionController.isCarrierRoamingNbIotInActivityTimerStarted());
+        assertFalse(mTestSatelliteSessionController.isEsosInActivityTimerStarted());
 
         mTestSatelliteSessionController.setDeviceAlignedWithSatellite(false);
 
         // Verify that the ESOS inactivity timer is started.
-        assertTrue(mTestSatelliteSessionController.isCarrierRoamingNbIotInActivityTimerStarted());
+        assertTrue(mTestSatelliteSessionController.isEsosInActivityTimerStarted());
 
         // Time shift to cause timeout
         moveTimeForward(ESOS_INACTIVITY_TIMEOUT_SEC * 1000);
@@ -366,6 +427,543 @@
     }
 
     @Test
+    public void testEsosP2pSmsInactivityTimerCase1() {
+        // Send eSOS and SMS
+        // After 10 minutes SatelliteSessionController moves to idle
+        // TN network reports IN_SERVICE
+        // Report the callback only and don't auto exit
+
+        long passedTime = 0;
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        // Support ESOS
+        when(mMockSatelliteController.isSatelliteEsosSupported(anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(true);
+        // Support P2P_SMS
+        when(mMockSatelliteController.isSatelliteRoamingP2pSmSSupported(
+                anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getSupportedServicesOnCarrierRoamingNtn(anyInt()))
+                .thenReturn(new int[]{
+                        NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+                        NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY});
+
+        // Setup carrier config for timer values
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt(KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT,
+                ESOS_INACTIVITY_TIMEOUT_SEC);
+        bundle.putInt(KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT,
+                P2P_SMS_INACTIVITY_TIMEOUT_SEC);
+        when(mMockSatelliteController.getPersistableBundle(anyInt())).thenReturn(bundle);
+
+        // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+        assertNotNull(mTestSatelliteSessionController);
+        mTestSatelliteSessionController.setSatelliteEnabledForNtnOnlySubscription(false);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        // Set up Datagram SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE
+        setupDatagramTransferringState(true);
+
+        moveToNotConnectedState();
+
+        // Notify datagram controller is in WAITING_TO_CONNECT.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                DATAGRAM_TYPE_UNKNOWN);
+        processAllMessages();
+
+        // Verify that ESOS, P2P_SMS timer are not started.
+        verifyEsosP2pSmsInactivityTimer(false, false);
+
+        // Sent ESOS
+        sendMessage(DATAGRAM_TYPE_SOS_MESSAGE);
+
+        // Verify that ESOS, P2P_SMS timer are started.
+        verifyEsosP2pSmsInactivityTimer(true, true);
+
+        // Sent SMS
+        sendMessage(DATAGRAM_TYPE_SMS);
+
+        // Verify that ESOS, P2P_SMS timer are started.
+        verifyEsosP2pSmsInactivityTimer(true, true);
+
+        // Time shift to cause P2P_SMS timeout
+        passedTime = P2P_SMS_INACTIVITY_TIMEOUT_SEC * 1000;
+        moveTimeForward(P2P_SMS_INACTIVITY_TIMEOUT_SEC * 1000);
+        processAllMessages();
+
+        // Verify that keep ESOS timer, expired P2P_SMS timer.
+        // NOT_CONNECTED state, satellite disabling not called.
+        verifyEsosP2pSmsInactivityTimer(true, false);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockSatelliteController, never()).requestSatelliteEnabled(
+                eq(false), eq(false), eq(true), any(IIntegerConsumer.Stub.class));
+
+        // Time shift to cause ESOS timeout
+        moveTimeForward(ESOS_INACTIVITY_TIMEOUT_SEC * 1000 - passedTime);
+        processAllMessages();
+
+        // Verify that expired ESOS and P2P_SMS timer
+        // reported IDLE state, not called satellite disabling.
+        verifyEsosP2pSmsInactivityTimer(false, false);
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        verify(mMockSatelliteController, never()).requestSatelliteEnabled(
+                eq(false), eq(false), eq(true), any(IIntegerConsumer.Stub.class));
+    }
+
+    @Test
+    public void testEsosP2pSmsInactivityTimerCase2() {
+        // Send eSOS and SMS
+        // Send SMS after 3 mins
+        // Send SMS after 1 mins
+        // After 10 minutes SatelliteSessionController moves to idle
+        // TN network reports IN_SERVICE
+        // Report the callback only and don't auto exit
+
+        long passedTime = 0;
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        // Support ESOS
+        when(mMockSatelliteController.isSatelliteEsosSupported(anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(true);
+        // Support P2P_SMS
+        when(mMockSatelliteController.isSatelliteRoamingP2pSmSSupported(
+                anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getSupportedServicesOnCarrierRoamingNtn(anyInt()))
+                .thenReturn(new int[]{
+                        NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+                        NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY});
+
+        // Setup carrier config for timer values
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt(KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT,
+                ESOS_INACTIVITY_TIMEOUT_SEC);
+        bundle.putInt(KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT,
+                P2P_SMS_INACTIVITY_TIMEOUT_SEC);
+        when(mMockSatelliteController.getPersistableBundle(anyInt())).thenReturn(bundle);
+
+        // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+        assertNotNull(mTestSatelliteSessionController);
+        mTestSatelliteSessionController.setSatelliteEnabledForNtnOnlySubscription(false);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        // Set up Datagram SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE
+        setupDatagramTransferringState(true);
+
+        moveToNotConnectedState();
+
+        // Notify datagram controller is in WAITING_TO_CONNECT.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                DATAGRAM_TYPE_UNKNOWN);
+        processAllMessages();
+
+        // Verify that ESOS, P2P_SMS timer are not started.
+        verifyEsosP2pSmsInactivityTimer(false, false);
+
+        // Sent ESOS, SMS
+        sendMessage(DATAGRAM_TYPE_SOS_MESSAGE);
+        sendMessage(DATAGRAM_TYPE_SMS);
+        // Verify that ESOS, P2P_SMS timer are started.
+        verifyEsosP2pSmsInactivityTimer(true, true);
+
+        // Sent SMS again after 3 mins
+        passedTime = 3 * 60 * 1000;
+        moveTimeForward(3 * 60 * 1000);
+        processAllMessages();
+        sendMessage(DATAGRAM_TYPE_SMS);
+
+        // Sent SMS again after 1 mins
+        passedTime += 1 * 60 * 1000;
+        moveTimeForward(1 * 60 * 1000);
+        processAllMessages();
+        sendMessage(DATAGRAM_TYPE_SMS);
+
+        // Time shift to cause ESOS timeout
+        moveTimeForward(ESOS_INACTIVITY_TIMEOUT_SEC * 1000 - passedTime);
+        processAllMessages();
+
+        // Verify that expired ESOS and P2P_SMS timer
+        // reported IDLE state, not called satellite disabling.
+        verifyEsosP2pSmsInactivityTimer(false, false);
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        verify(mMockSatelliteController, never()).requestSatelliteEnabled(
+                eq(false), eq(false), eq(true), any(IIntegerConsumer.Stub.class));
+    }
+
+    @Test
+    public void testEsosP2pSmsInactivityTimerCase3() {
+        // Send eSOS and SMS
+        // Send eSOS after 5 mins
+        // After 15 minutes SatelliteSessionController moves to idle
+        // TN network reports IN_SERVICE
+        // Report the callback only and don't auto exit
+
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        // Support ESOS
+        when(mMockSatelliteController.isSatelliteEsosSupported(anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(true);
+        // Support P2P_SMS
+        when(mMockSatelliteController.isSatelliteRoamingP2pSmSSupported(
+                anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getSupportedServicesOnCarrierRoamingNtn(anyInt()))
+                .thenReturn(new int[]{
+                        NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+                        NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY});
+
+        // Setup carrier config for timer values
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt(KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT,
+                ESOS_INACTIVITY_TIMEOUT_SEC);
+        bundle.putInt(KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT,
+                P2P_SMS_INACTIVITY_TIMEOUT_SEC);
+        when(mMockSatelliteController.getPersistableBundle(anyInt())).thenReturn(bundle);
+
+        // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+        assertNotNull(mTestSatelliteSessionController);
+        mTestSatelliteSessionController.setSatelliteEnabledForNtnOnlySubscription(false);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        // Set up Datagram SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE
+        setupDatagramTransferringState(true);
+
+        moveToNotConnectedState();
+
+        // Notify datagram controller is in WAITING_TO_CONNECT.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                DATAGRAM_TYPE_UNKNOWN);
+        processAllMessages();
+
+        // Verify that ESOS, P2P_SMS timer are not started.
+        verifyEsosP2pSmsInactivityTimer(false, false);
+
+        // Sent ESOS
+        sendMessage(DATAGRAM_TYPE_SOS_MESSAGE);
+        // Verify that ESOS, P2P_SMS timer are started.
+        verifyEsosP2pSmsInactivityTimer(true, true);
+
+        // Sent ESOS after 5 mins
+        moveTimeForward(3 * 60 * 1000);
+        processAllMessages();
+        sendMessage(DATAGRAM_TYPE_SOS_MESSAGE);
+
+        // Time shift to cause ESOS timeout
+        moveTimeForward(ESOS_INACTIVITY_TIMEOUT_SEC * 1000);
+        processAllMessages();
+
+        // Verify that expired ESOS and P2P_SMS timer
+        // reported IDLE state, not called satellite disabling.
+        verifyEsosP2pSmsInactivityTimer(false, false);
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        verify(mMockSatelliteController, never()).requestSatelliteEnabled(
+                eq(false), eq(false), eq(true), any(IIntegerConsumer.Stub.class));
+    }
+
+    @Test
+    public void testEsosP2pSmsInactivityTimerCase4() {
+        // Send SMS
+        // Send SMS after 2 mins
+        // After 3 minutes SatelliteSessionController moves to idle
+        // TN network reports IN_SERVICE
+        // Report the callback only and auto exit
+
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        // Support ESOS, Satellite is not in emergency mode
+        when(mMockSatelliteController.isSatelliteEsosSupported(anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(false);
+        // Support P2P_SMS
+        when(mMockSatelliteController.isSatelliteRoamingP2pSmSSupported(
+                anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getSupportedServicesOnCarrierRoamingNtn(anyInt()))
+                .thenReturn(new int[]{
+                        NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+                        NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY});
+
+        // Setup carrier config for timer values
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt(KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT,
+                ESOS_INACTIVITY_TIMEOUT_SEC);
+        bundle.putInt(KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT,
+                P2P_SMS_INACTIVITY_TIMEOUT_SEC);
+        when(mMockSatelliteController.getPersistableBundle(anyInt())).thenReturn(bundle);
+
+        // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+        assertNotNull(mTestSatelliteSessionController);
+        mTestSatelliteSessionController.setSatelliteEnabledForNtnOnlySubscription(false);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        // Set up Datagram SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE
+        setupDatagramTransferringState(true);
+
+        moveToNotConnectedState();
+
+        // Notify datagram controller is in WAITING_TO_CONNECT.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                DATAGRAM_TYPE_UNKNOWN);
+        processAllMessages();
+
+        // Verify that ESOS, P2P_SMS timer are not started.
+        verifyEsosP2pSmsInactivityTimer(false, false);
+
+        // Sent SMS
+        sendMessage(DATAGRAM_TYPE_SMS);
+
+        // Verify that ESOS is not started, P2P_SMS timer is started.
+        verifyEsosP2pSmsInactivityTimer(false, true);
+
+        // Sent SMS again after 2 mins
+        moveTimeForward(2 * 60 * 1000);
+        processAllMessages();
+        sendMessage(DATAGRAM_TYPE_SMS);
+
+        // Verify that ESOS is not started, P2P_SMS timer is started.
+        verifyEsosP2pSmsInactivityTimer(false, true);
+
+        // Time shift to cause P2P_SMS timeout
+        moveTimeForward(P2P_SMS_INACTIVITY_TIMEOUT_SEC * 1000);
+        processAllMessages();
+    }
+
+    @Test
+    public void testEsosP2pSmsInactivityTimerCase5() {
+        // Send SMS
+        // Send ESOS after 2 mins
+        // After 12 minutes SatelliteSessionController moves to idle
+        // TN network reports IN_SERVICE
+        // Report the callback only and don'tauto exit
+
+        long passedTime = 0;
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        // Support ESOS
+        when(mMockSatelliteController.isSatelliteEsosSupported(anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(true);
+        // Support P2P_SMS
+        when(mMockSatelliteController.isSatelliteRoamingP2pSmSSupported(
+                anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getSupportedServicesOnCarrierRoamingNtn(anyInt()))
+                .thenReturn(new int[]{
+                        NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+                        NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY});
+
+        // Setup carrier config for timer values
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt(KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT,
+                ESOS_INACTIVITY_TIMEOUT_SEC);
+        bundle.putInt(KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT,
+                P2P_SMS_INACTIVITY_TIMEOUT_SEC);
+        when(mMockSatelliteController.getPersistableBundle(anyInt())).thenReturn(bundle);
+
+        // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+        assertNotNull(mTestSatelliteSessionController);
+        mTestSatelliteSessionController.setSatelliteEnabledForNtnOnlySubscription(false);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        // Set up Datagram SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE
+        setupDatagramTransferringState(true);
+
+        moveToNotConnectedState();
+
+        // Notify datagram controller is in WAITING_TO_CONNECT.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                DATAGRAM_TYPE_UNKNOWN);
+        processAllMessages();
+
+        // Verify that ESOS, P2P_SMS timer are not started.
+        verifyEsosP2pSmsInactivityTimer(false, false);
+
+        // Sent SMS
+        sendMessage(DATAGRAM_TYPE_SMS);
+
+        // Verify that ESOS is not started, P2P_SMS timer is started.
+        verifyEsosP2pSmsInactivityTimer(true, true);
+
+        // Sent ESOS again after 2 mins
+        passedTime = 2 * 60 * 1000;
+        moveTimeForward(2 * 60 * 1000);
+        processAllMessages();
+        sendMessage(DATAGRAM_TYPE_SOS_MESSAGE);
+
+        // Verify that ESOS is not started, P2P_SMS timer is started.
+        verifyEsosP2pSmsInactivityTimer(true, true);
+
+        // Time shift
+        moveTimeForward(ESOS_INACTIVITY_TIMEOUT_SEC * 1000 - passedTime);
+        processAllMessages();
+        verifyEsosP2pSmsInactivityTimer(true, false);
+
+        // Time shift
+        moveTimeForward(passedTime);
+        processAllMessages();
+
+        // Verify that expired P2P_SMS timer
+        // reported IDLE state, called satellite disabling.
+        verifyEsosP2pSmsInactivityTimer(false, false);
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        verify(mMockSatelliteController, never()).requestSatelliteEnabled(
+                eq(false), eq(false), eq(true), any(IIntegerConsumer.Stub.class));
+    }
+
+    @Test
+    public void testEsosP2pSmsInactivityTimerInConnectedState() {
+        // Send eSOS and SMS
+        // After 10 minutes SatelliteSessionController moves to idle
+        // TN network reports IN_SERVICE
+        // Report the callback only and don't auto exit
+
+        long passedTime = 0;
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        // Support ESOS
+        when(mMockSatelliteController.isSatelliteEsosSupported(anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(true);
+        // Support P2P_SMS
+        when(mMockSatelliteController.isSatelliteRoamingP2pSmSSupported(
+                anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getSupportedServicesOnCarrierRoamingNtn(anyInt()))
+                .thenReturn(new int[]{
+                        NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+                        NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY});
+
+        // Setup carrier config for timer values
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt(KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT,
+                ESOS_INACTIVITY_TIMEOUT_SEC);
+        bundle.putInt(KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT,
+                P2P_SMS_INACTIVITY_TIMEOUT_SEC);
+        when(mMockSatelliteController.getPersistableBundle(anyInt())).thenReturn(bundle);
+
+        // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+        assertNotNull(mTestSatelliteSessionController);
+        mTestSatelliteSessionController.setSatelliteEnabledForNtnOnlySubscription(false);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        // Set up Datagram SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE
+        setupDatagramTransferringState(true);
+
+        moveToNotConnectedState();
+        moveNotConnectedToConnectedState();
+
+        // Verify that ESOS, P2P_SMS timer are started.
+        verifyEsosP2pSmsInactivityTimer(true, true);
+
+        // Sent ESOS
+        // CONNECTED -> TRANSFERRING -> CONNECTED
+        sendMessage(DATAGRAM_TYPE_SOS_MESSAGE);
+
+        // Verify that ESOS, P2P_SMS timer are restarted.
+        verifyEsosP2pSmsInactivityTimer(true, true);
+
+        // Sent SMS
+        // CONNECTED -> TRANSFERRING -> CONNECTED
+        sendMessage(DATAGRAM_TYPE_SMS);
+
+        // Verify that ESOS, P2P_SMS timer are restarted.
+        verifyEsosP2pSmsInactivityTimer(true, true);
+
+        // Time shift to cause P2P_SMS timeout
+        passedTime = P2P_SMS_INACTIVITY_TIMEOUT_SEC * 1000;
+        moveTimeForward(P2P_SMS_INACTIVITY_TIMEOUT_SEC * 1000);
+        processAllMessages();
+
+        // Verify that keep ESOS timer, expired P2P_SMS timer.
+        // CONNECTED state
+        verifyEsosP2pSmsInactivityTimer(true, false);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Time shift to cause ESOS timeout
+        moveTimeForward(ESOS_INACTIVITY_TIMEOUT_SEC * 1000 - passedTime);
+        processAllMessages();
+
+        // Verify that expired ESOS and P2P_SMS timer
+        // reported IDLE state.
+        verifyEsosP2pSmsInactivityTimer(false, false);
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+    }
+
+    @Test
+    public void testDisableSatelliteWhenCellularModemEnabledInIdleMode() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        doNothing().when(mDeviceStateMonitor).registerForScreenStateChanged(
+                eq(mTestSatelliteSessionController.getHandler()), anyInt(), any());
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(false);
+        when(mPhone.getServiceState()).thenReturn(mMockServiceState);
+        setUpResponseForRequestSatelliteEnabled(SATELLITE_RESULT_SUCCESS);
+
+        // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+        assertNotNull(mTestSatelliteSessionController);
+        mTestSatelliteSessionController.setSatelliteEnabledForNtnOnlySubscription(false);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Conditions for operation
+        boolean isEmergency = true;
+        // Cellular network is not IN_SERVICE and emergency only.
+        // Satellite request is emergency and emergency communication was established.
+        // Disabling satellite was not allowed
+        when(mMockServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mMockServiceState.getDataRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mMockServiceState.isEmergencyOnly()).thenReturn(false);
+        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(isEmergency);
+        when(mMockDatagramController.isEmergencyCommunicationEstablished()).thenReturn(true);
+        when(mMockSatelliteController.turnOffSatelliteSessionForEmergencyCall(
+                anyInt())).thenReturn(false);
+
+        moveToIdleState();
+
+        // Cellular network is not in STATE_IN_SERVICE or emergency only.
+        // Should not disable satellite
+        verify(mMockSatelliteController, never()).requestSatelliteEnabled(
+                eq(false), eq(false), eq(isEmergency), any(IIntegerConsumer.Stub.class));
+        verify(mMockSessionMetricsStats, never()).addCountOfAutoExitDueToTnNetwork();
+
+        // Notify cellular service is in STATE_IN_SERVICE.
+        ServiceState serviceState = new ServiceState();
+        serviceState.setVoiceRegState(ServiceState.STATE_IN_SERVICE);
+        serviceState.setDataRegState(ServiceState.STATE_OUT_OF_SERVICE);
+        serviceState.setEmergencyOnly(false);
+        mTestSatelliteSessionController.onCellularServiceStateChanged(serviceState);
+        processAllMessages();
+
+        // Satellite is in emergency mode and emergency communication was established.
+        // Should not disable satellite
+        verify(mMockSatelliteController, never()).requestSatelliteEnabled(
+                eq(false), eq(false), eq(isEmergency), any(IIntegerConsumer.Stub.class));
+        verify(mMockSessionMetricsStats, never()).addCountOfAutoExitDueToTnNetwork();
+
+        // Satellite is in emergency mode but emergency communication was not established.
+        // Disabling satellite was not allowed
+        when(mMockDatagramController.isEmergencyCommunicationEstablished()).thenReturn(false);
+        when(mMockSatelliteController.turnOffSatelliteSessionForEmergencyCall(
+                anyInt())).thenReturn(false);
+        mTestSatelliteSessionController.onCellularServiceStateChanged(serviceState);
+        processAllMessages();
+
+        // Should not disable satellite
+        verify(mMockSatelliteController, never()).requestSatelliteEnabled(
+                eq(false), eq(false), eq(isEmergency), any(IIntegerConsumer.Stub.class));
+        verify(mMockSessionMetricsStats, never()).addCountOfAutoExitDueToTnNetwork();
+        // Satellite is in emergency mode but emergency communication was not established.
+        // Disabling satellite was allowed
+        when(mMockSatelliteController.turnOffSatelliteSessionForEmergencyCall(
+                anyInt())).thenReturn(true);
+        mTestSatelliteSessionController.onCellularServiceStateChanged(serviceState);
+        processAllMessages();
+
+        // Should disable satellite
+        verify(mMockSatelliteController).requestSatelliteEnabled(
+                eq(false), eq(false), eq(isEmergency), any(IIntegerConsumer.Stub.class));
+        verify(mMockSessionMetricsStats, times(1)).addCountOfAutoExitDueToTnNetwork();
+    }
+
+    @Test
     public void testStateTransition() {
         /**
          * Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
@@ -401,7 +999,8 @@
 
         // Start sending datagrams
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
-                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
@@ -413,7 +1012,8 @@
         // Sending datagrams failed
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to IDLE state.
@@ -425,7 +1025,8 @@
         // Start sending datagrams again
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
@@ -437,7 +1038,8 @@
         // Sending datagrams is successful and done.
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to LISTENING state.
@@ -450,7 +1052,8 @@
         // Start receiving datagrams
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
@@ -462,7 +1065,8 @@
 
         // Receiving datagrams is successful and done.
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to LISTENING state.
@@ -475,7 +1079,8 @@
         // Start receiving datagrams again
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
@@ -488,7 +1093,8 @@
         // Receiving datagrams failed.
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to IDLE state.
@@ -500,7 +1106,8 @@
         // Start receiving datagrams again
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
@@ -511,7 +1118,8 @@
 
         // Receiving datagrams is successful and done.
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to LISTENING state.
@@ -535,7 +1143,8 @@
         // Start receiving datagrams again
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
@@ -547,7 +1156,8 @@
         // Start sending datagrams
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should stay at TRANSFERRING state.
@@ -558,7 +1168,8 @@
         // Receiving datagrams failed.
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should stay at TRANSFERRING state instead of moving to IDLE
@@ -570,7 +1181,8 @@
         // Start receiving datagrams again.
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should stay at TRANSFERRING state.
@@ -581,7 +1193,8 @@
         // Sending datagrams failed.
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should stay at TRANSFERRING state instead of moving to IDLE
@@ -657,7 +1270,8 @@
 
         // Start sending datagrams
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
-                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // The datagram sending event should be ignored.
@@ -680,7 +1294,8 @@
 
         // Start sending datagrams
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
-                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
@@ -695,7 +1310,8 @@
         // Sending datagrams failed
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to CONNECTED state.
@@ -710,7 +1326,8 @@
         // Start sending datagrams again
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
@@ -725,7 +1342,8 @@
         // Sending datagrams is successful and done.
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to CONNECTED state.
@@ -740,7 +1358,8 @@
         // Start receiving datagrams
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
@@ -754,7 +1373,8 @@
 
         // Receiving datagrams is successful and done.
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to CONNECTED state.
@@ -769,7 +1389,8 @@
         // Start receiving datagrams
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
@@ -783,7 +1404,8 @@
 
         // Receiving datagrams is successful and done.
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to CONNECTED state.
@@ -811,7 +1433,8 @@
         // Start sending datagrams
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to NOT_CONNECTED state.
@@ -903,22 +1526,6 @@
                 SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
         assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
 
-        // Set up error response for the request to disable cellular scanning
-        mSatelliteModemInterface.setErrorCode(SatelliteManager.SATELLITE_RESULT_MODEM_ERROR);
-
-        // Start sending datagrams
-        mTestSatelliteSessionController.onDatagramTransferStateChanged(
-                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
-        processAllMessages();
-
-        // SatelliteSessionController should stay at IDLE state because it failed to disable
-        // cellular scanning.
-        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
-        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
-
-        mSatelliteModemInterface.setErrorCode(SatelliteManager.SATELLITE_RESULT_SUCCESS);
-
         // Power off the modem.
         mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false);
         processAllMessages();
@@ -965,7 +1572,8 @@
         // Start sending datagrams and the NB-IOT inactivity timer should be stopped.
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
         processAllMessages();
 
@@ -977,10 +1585,12 @@
         // The NB-IOT inactivity timer should be started.
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
         assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
 
@@ -1230,9 +1840,429 @@
         assertEmergencyModeChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
     }
 
-    private void setupDatagramTransferringState(boolean isTransferring) {
-        when(mMockDatagramController.isSendingInIdleState()).thenReturn(isTransferring);
-        when(mMockDatagramController.isPollingInIdleState()).thenReturn(isTransferring);
+    @Test
+    public void testNotConnectedToIdleToNotConnectedStateTransition() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        when(mContext.getResources().getBoolean(
+                 R.bool.config_satellite_modem_support_concurrent_tn_scanning)).thenReturn(true);
+
+        assertNotNull(mTestSatelliteSessionController);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        setupDatagramTransferringState(true);
+
+        powerOnSatelliteModem();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be started.
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Wait for timeout
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state, but the state transition will
+        // be hidden because device does not support satellite modem IDLE state.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be stopped.
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        // The transition is hidden and thus DatagramController is not notified.
+        verify(mMockDatagramController, never()).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        clearInvocations(mMockDatagramController);
+
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
+        processAllMessages();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+    }
+
+    @Test
+    public void testNotConnectedToIdleToTransferringStateTransition() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        when(mContext.getResources().getBoolean(
+                 R.bool.config_satellite_modem_support_concurrent_tn_scanning)).thenReturn(true);
+
+        assertNotNull(mTestSatelliteSessionController);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        setupDatagramTransferringState(true);
+
+        powerOnSatelliteModem();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be started.
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Wait for timeout
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state, but the state transition will
+        // be hidden because device does not support satellite modem IDLE state.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be stopped.
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        // The transition is hidden and thus DatagramController is not notified.
+        verify(mMockDatagramController, never()).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        clearInvocations(mMockDatagramController);
+
+        // Modem report CONNECTED state
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+
+        // SatelliteSessionController should stay in IDLE state, but clients should be
+        // notified that modem has moved to CONNECTED state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        clearInvocations(mMockDatagramController);
+    }
+
+    @Test
+    public void testConnectedToIdleToTransferringStateTransition() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        when(mContext.getResources().getBoolean(
+                 R.bool.config_satellite_modem_support_concurrent_tn_scanning)).thenReturn(true);
+
+        assertNotNull(mTestSatelliteSessionController);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        setupDatagramTransferringState(false);
+
+        powerOnSatelliteModem();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Modem report CONNECTED state
+        setupDatagramTransferringState(true);
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+
+        // SatelliteSessionController should move to CONNECTED state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be started.
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Wait for timeout
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state, but the state transition will
+        // be hidden because device does not support satellite modem IDLE state.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be stopped.
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        // The transition is hidden and thus DatagramController is not notified.
+        verify(mMockDatagramController, never()).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        clearInvocations(mMockDatagramController);
+
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        clearInvocations(mMockDatagramController);
+    }
+
+    @Test
+    public void testConnectedToIdleToNotConnectedStateTransition() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        when(mContext.getResources().getBoolean(
+                 R.bool.config_satellite_modem_support_concurrent_tn_scanning)).thenReturn(true);
+
+        assertNotNull(mTestSatelliteSessionController);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        setupDatagramTransferringState(false);
+
+        powerOnSatelliteModem();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Modem report CONNECTED state
+        setupDatagramTransferringState(true);
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+
+        // SatelliteSessionController should move to CONNECTED state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be started.
+        assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Wait for timeout
+        moveTimeForward(TEST_SATELLITE_TIMEOUT_MILLIS);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state, but the state transition will
+        // be hidden because device does not support satellite modem IDLE state.
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        // The inactivity timer should be stopped.
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        // The transition is hidden and thus DatagramController is not notified.
+        verify(mMockDatagramController, never()).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        clearInvocations(mMockDatagramController);
+
+        // Modem report NOT_CONNECTED state
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        processAllMessages();
+
+        // SatelliteSessionController should stay in IDLE state, but the clients
+        // should be notified that modem has moved to NOT_CONNECTED state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
+        processAllMessages();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+    }
+
+    @Test
+    public void testP2pSmsInactivityTimerTimedOut_tnScanningNotSupported() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+
+        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(false);
+        when(mMockSatelliteController.isSatelliteRoamingP2pSmSSupported(
+                anyInt())).thenReturn(true);
+        when(mMockSatelliteController.getSupportedServicesOnCarrierRoamingNtn(anyInt()))
+                .thenReturn(new int[]{
+                        NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+                        NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY});
+        when(mMockSatelliteController.isInCarrierRoamingNbIotNtn()).thenReturn(true);
+        Resources resources = mContext.getResources();
+        when(resources.getBoolean(
+                R.bool.config_satellite_allow_tn_scanning_during_satellite_session))
+                .thenReturn(false);
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt(KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT,
+                P2P_SMS_INACTIVITY_TIMEOUT_SEC);
+        when(mMockSatelliteController.getPersistableBundle(anyInt())).thenReturn(bundle);
+
+        // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+        assertNotNull(mTestSatelliteSessionController);
+        mTestSatelliteSessionController.setSatelliteEnabledForNtnOnlySubscription(false);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        setupDatagramTransferringState(true);
+
+        moveToNotConnectedState();
+
+        // Verify that the P2P SMS inactivity timer is started.
+        assertTrue(mTestSatelliteSessionController.isP2pSmsInActivityTimerStarted());
+
+        // Time shift to cause timeout
+        moveTimeForward(P2P_SMS_INACTIVITY_TIMEOUT_SEC * 1000);
+        processAllMessages();
+
+        // Should disable satellite
+        verify(mMockSatelliteController).requestSatelliteEnabled(
+                eq(false), eq(false), eq(false), any(IIntegerConsumer.Stub.class));
+    }
+
+    @Test
+    public void testSetDeviceAlignedWithSatellite_updatesMaxInactivityDuration() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        setUserInactivityStart();
+
+        mTestSatelliteSessionController.setDeviceAlignedWithSatellite(true);
+
+        verify(mMockSessionMetricsStats, times(1)).updateMaxInactivityDurationSec(anyInt());
+    }
+
+    @Test
+    public void
+            testSetDeviceAlignedWithSatellite_setsInactivityStartTimestampUndefinedAfterUpdate() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        setUserInactivityStart();
+        mTestSatelliteSessionController.setDeviceAlignedWithSatellite(true);
+
+        mTestSatelliteSessionController.setDeviceAlignedWithSatellite(true);
+
+        // There should be only one call to updateMaxInactivityDurationSec since the inactivity
+        // start timestamp is reset to undefined.
+        verify(mMockSessionMetricsStats, times(1)).updateMaxInactivityDurationSec(anyInt());
+    }
+
+    @Test
+    public void
+            testSetDeviceAlignedWithSatellite_noInactivityStart_noUpdateMaxInactivityDuration() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+
+        mTestSatelliteSessionController.setDeviceAlignedWithSatellite(true);
+
+        verify(mMockSessionMetricsStats, times(0)).updateMaxInactivityDurationSec(anyInt());
+    }
+
+    @Test
+    public void testSetDeviceAlignedWithSatellite_flagOff_noUpdateMaxInactivityDuration() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(false);
+        setUserInactivityStart();
+
+        mTestSatelliteSessionController.setDeviceAlignedWithSatellite(true);
+
+        verify(mMockSessionMetricsStats, times(0)).updateMaxInactivityDurationSec(anyInt());
+    }
+
+    @Test
+    public void testOnDatagramTransferStateChanged_notIdle_updatesMaxInactivityDuration() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        setUserInactivityStart();
+
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
+
+        // Since both the send and receive datagram transfer state is not idle, the max inactivity
+        // duration should be updated.
+        verify(mMockSessionMetricsStats, times(1)).updateMaxInactivityDurationSec(anyInt());
+    }
+
+    @Test
+    public void testOnDatagramTransferStateChanged_idle_updatesMaxInactivityDuration() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        setUserInactivityStart();
+        moveToIdleState();
+
+        moveToPowerOffState();
+
+        verify(mMockSessionMetricsStats, times(1)).updateMaxInactivityDurationSec(anyInt());
+    }
+
+    private void setUserInactivityStart() {
+        // Set DatagramTransferState to idle and unaligned with satellite to define inactivity start
+        // timestamp
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
+        mTestSatelliteSessionController.setDeviceAlignedWithSatellite(false);
+    }
+
+    private void verifyEsosP2pSmsInactivityTimer(boolean esosTimer, boolean p2pSmsTimer) {
+        assertEquals(mTestSatelliteSessionController.isEsosInActivityTimerStarted(), esosTimer);
+        assertEquals(mTestSatelliteSessionController.isP2pSmsInActivityTimerStarted(),
+                p2pSmsTimer);
+    }
+
+    private void sendMessage(@SatelliteManager.DatagramType int datagramType) {
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, datagramType);
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, datagramType);
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, datagramType);
+
+        processAllMessages();
+    }
+
+    private void setupDatagramTransferringState(boolean isIdle) {
+        when(mMockDatagramController.isSendingInIdleState()).thenReturn(isIdle);
+        when(mMockDatagramController.isPollingInIdleState()).thenReturn(isIdle);
     }
 
     private void powerOnSatelliteModem() {
@@ -1293,7 +2323,8 @@
         assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
         // Start sending datagrams
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
-                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to TRANSFERRING state.
@@ -1308,7 +2339,8 @@
         // Sending datagrams is successful and done.
         mTestSatelliteSessionController.onDatagramTransferStateChanged(
                 SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                DATAGRAM_TYPE_UNKNOWN);
         processAllMessages();
 
         // SatelliteSessionController should move to LISTENING state.
@@ -1419,6 +2451,8 @@
     }
 
     private static class TestSatelliteSessionController extends SatelliteSessionController {
+        boolean mSatelliteEnabledForNtnOnlySubscription = true;
+
         TestSatelliteSessionController(Context context, Looper looper, FeatureFlags featureFlags,
                 boolean isSatelliteSupported,
                 SatelliteModemInterface satelliteModemInterface) {
@@ -1441,16 +2475,12 @@
             return hasDeferredMessages(event);
         }
 
-        boolean isScreenOffInActivityTimerStarted() {
-            return hasMessages(EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT);
-        }
-
-        boolean isCarrierRoamingNbIotInActivityTimerStarted() {
-            return hasMessages(EVENT_CARRIER_ROAMING_NB_IOT_INACTIVITY_TIMER_TIMED_OUT);
-        }
-
         protected boolean isSatelliteEnabledForNtnOnlySubscription() {
-            return true;
+            return mSatelliteEnabledForNtnOnlySubscription;
+        }
+
+        void setSatelliteEnabledForNtnOnlySubscription(boolean enabled) {
+            mSatelliteEnabledForNtnOnlySubscription = false;
         }
     }
 
@@ -1493,6 +2523,11 @@
             logd("onRegistrationFailure: causeCode=" + causeCode);
         }
 
+        @Override
+        public void onTerrestrialNetworkAvailableChanged(boolean isAvailable) {
+            logd("onTerrestrialNetworkAvailableChanged: isAvailable=" + isAvailable);
+        }
+
         public boolean waitUntilResultForModemStateChanged() {
             try {
                 if (!mSemaphoreForModemStateChanged.tryAcquire(EVENT_PROCESSING_TIME_MILLIS,
@@ -1587,4 +2622,14 @@
         msg.obj = new AsyncResult(null, screenOn, null);
         h.sendMessage(msg);
     }
+
+    private void setUpResponseForRequestSatelliteEnabled(
+            @SatelliteManager.SatelliteResult int expectedResult) {
+        doAnswer(invocation -> {
+            IIntegerConsumer integerConsumer = invocation.getArgument(3);
+            integerConsumer.accept(expectedResult);
+            return null;
+        }).when(mMockSatelliteController).requestSatelliteEnabled(anyBoolean(), anyBoolean(),
+                anyBoolean(), any(IIntegerConsumer.class));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
index 4b1b4a5..5e560bc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
@@ -457,7 +457,6 @@
         doReturn(1).when(mUiccController).convertToPublicCardId(eq(FAKE_ICCID1));
         doReturn(2).when(mUiccController).convertToPublicCardId(eq(FAKE_ICCID2));
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
-        when(mFeatureFlags.dataOnlyCellularService()).thenReturn(true);
         when(mFeatureFlags.supportPsimToEsimConversion()).thenReturn(true);
         when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
         mDatabaseManagerUT = new SubscriptionDatabaseManager(mContext, Looper.myLooper(),
diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
index 925cf71..f639a51 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
@@ -24,8 +24,6 @@
 import android.telephony.UiccAccessRule;
 import android.telephony.ims.ImsMmTelManager;
 
-import com.android.internal.telephony.flags.Flags;
-
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -160,7 +158,6 @@
 
     @Test
     public void testSubscriptionInfoInternalSetAndGet() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE);
         assertThat(mSubInfo.getSubscriptionId()).isEqualTo(1);
         assertThat(mSubInfo.getIccId()).isEqualTo(SubscriptionDatabaseManagerTest.FAKE_ICCID1);
         assertThat(mSubInfo.getSimSlotIndex()).isEqualTo(0);
@@ -271,7 +268,6 @@
 
     @Test
     public void testConvertToSubscriptionInfo() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE);
         SubscriptionInfo subInfo = mSubInfo.toSubscriptionInfo();
 
         assertThat(subInfo.getSubscriptionId()).isEqualTo(1);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
index 65790f8..fa72021 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
@@ -52,6 +52,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -70,6 +71,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.PropertyInvalidatedCache;
 import android.compat.testing.PlatformCompatChangeRule;
@@ -1123,9 +1125,12 @@
     public void testGetAccessibleSubscriptionInfoList() {
         doReturn(true).when(mEuiccManager).isEnabled();
         insertSubscription(FAKE_SUBSCRIPTION_INFO2);
+        UserHandle user = UserHandle.of(ActivityManager.getCurrentUser());
 
         doReturn(true).when(mSubscriptionManager).canManageSubscription(
                 any(SubscriptionInfo.class), eq(CALLING_PACKAGE));
+        doReturn(true).when(mSubscriptionManager).canManageSubscriptionAsUser(
+                any(SubscriptionInfo.class), eq(CALLING_PACKAGE), any(UserHandle.class));
         // FAKE_SUBSCRIPTION_INFO2 is a not eSIM. So the list should be empty.
         assertThat(mSubscriptionManagerServiceUT.getAccessibleSubscriptionInfoList(
                 CALLING_PACKAGE)).isEmpty();
@@ -1138,6 +1143,8 @@
 
         doReturn(false).when(mSubscriptionManager).canManageSubscription(
                 any(SubscriptionInfo.class), eq(CALLING_PACKAGE));
+        doReturn(false).when(mSubscriptionManager).canManageSubscriptionAsUser(
+                any(SubscriptionInfo.class), eq(CALLING_PACKAGE), eq(user));
 
         doReturn(true).when(mEuiccManager).isEnabled();
         assertThat(mSubscriptionManagerServiceUT.getAccessibleSubscriptionInfoList(
@@ -1145,6 +1152,8 @@
 
         doReturn(true).when(mSubscriptionManager).canManageSubscription(
                 any(SubscriptionInfo.class), eq(CALLING_PACKAGE));
+        doReturn(true).when(mSubscriptionManager).canManageSubscriptionAsUser(
+                any(SubscriptionInfo.class), eq(CALLING_PACKAGE), eq(user));
         assertThat(mSubscriptionManagerServiceUT.getAccessibleSubscriptionInfoList(
                 CALLING_PACKAGE)).isEqualTo(List.of(new SubscriptionInfoInternal.Builder(
                         FAKE_SUBSCRIPTION_INFO1).setId(2).build().toSubscriptionInfo()));
@@ -1363,6 +1372,9 @@
         doReturn(true).when(mEuiccManager).isEnabled();
         doReturn(true).when(mSubscriptionManager).canManageSubscription(
                 any(SubscriptionInfo.class), eq(CALLING_PACKAGE));
+        UserHandle user = UserHandle.of(ActivityManager.getCurrentUser());
+        doReturn(true).when(mSubscriptionManager).canManageSubscriptionAsUser(
+                any(SubscriptionInfo.class), eq(CALLING_PACKAGE), eq(user));
         assertThat(mSubscriptionManagerServiceUT.getAccessibleSubscriptionInfoList(
                 CALLING_PACKAGE)).isEqualTo(List.of(FAKE_SUBSCRIPTION_INFO1.toSubscriptionInfo()));
         // Test getActiveSubIdList, System
@@ -1499,6 +1511,9 @@
         doReturn(true).when(mEuiccManager).isEnabled();
         doReturn(true).when(mSubscriptionManager).canManageSubscription(
                 any(SubscriptionInfo.class), eq(CALLING_PACKAGE));
+        UserHandle user = UserHandle.of(ActivityManager.getCurrentUser());
+        doReturn(true).when(mSubscriptionManager).canManageSubscriptionAsUser(
+                any(SubscriptionInfo.class), eq(CALLING_PACKAGE), eq(user));
         assertThat(mSubscriptionManagerServiceUT.getAccessibleSubscriptionInfoList(
                 CALLING_PACKAGE)).isEqualTo(List.of(FAKE_SUBSCRIPTION_INFO1.toSubscriptionInfo()));
         // Test getActiveSubIdList, System
@@ -3420,4 +3435,8 @@
         assertEquals(expectedPlmnList,
                 mSubscriptionManagerServiceUT.getSatelliteEntitlementPlmnList(subId));
     }
+
+    public void testIsSatelliteProvisionedForNonIpDatagram() {
+        assertFalse(mSubscriptionManagerServiceUT.isSatelliteProvisionedForNonIpDatagram(-1));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionPlanTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionPlanTest.java
new file mode 100644
index 0000000..2c13d3b
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionPlanTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.subscription;
+
+import static android.telephony.SubscriptionPlan.SUBSCRIPTION_STATUS_ACTIVE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.telephony.SubscriptionPlan;
+import android.testing.AndroidTestingRunner;
+
+import com.android.internal.telephony.flags.Flags;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Period;
+import java.time.ZonedDateTime;
+
+@RunWith(AndroidTestingRunner.class)
+public class SubscriptionPlanTest {
+    private static final ZonedDateTime ZONED_DATE_TIME_START =
+            ZonedDateTime.parse("2007-03-14T00:00:00.000Z");
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+    public void testBuilderExpirationDateSetsCorrectly() {
+        ZonedDateTime endDate = ZonedDateTime.parse("2024-11-07T00:00:00.000Z");
+
+        SubscriptionPlan planNonRecurring = SubscriptionPlan.Builder
+                .createNonrecurring(ZONED_DATE_TIME_START, endDate)
+                .setTitle("unit test")
+                .build();
+        SubscriptionPlan planRecurring = SubscriptionPlan.Builder
+                .createRecurring(ZONED_DATE_TIME_START, Period.ofMonths(1))
+                .setTitle("unit test")
+                .build();
+
+        assertThat(planNonRecurring.getPlanEndDate()).isEqualTo(endDate);
+        assertNull(planRecurring.getPlanEndDate());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+    public void testBuilderValidSubscriptionStatusSetsCorrectly() {
+        @SubscriptionPlan.SubscriptionStatus int status = SUBSCRIPTION_STATUS_ACTIVE;
+
+        SubscriptionPlan plan = SubscriptionPlan.Builder
+                .createRecurring(ZONED_DATE_TIME_START, Period.ofMonths(1))
+                .setSubscriptionStatus(status)
+                .setTitle("unit test")
+                .build();
+
+        assertThat(plan.getSubscriptionStatus()).isEqualTo(status);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SUBSCRIPTION_PLAN_ALLOW_STATUS_AND_END_DATE)
+    public void testBuilderInvalidSubscriptionStatusThrowsError() {
+        int minInvalid = -1;
+        int maxInvalid = 5;
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            SubscriptionPlan.Builder
+                    .createRecurring(ZONED_DATE_TIME_START, Period.ofMonths(1))
+                    .setSubscriptionStatus(minInvalid)
+                    .setTitle("unit test")
+                    .build();
+        });
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            SubscriptionPlan.Builder
+                    .createRecurring(ZONED_DATE_TIME_START, Period.ofMonths(1))
+                    .setSubscriptionStatus(maxInvalid)
+                    .setTitle("unit test")
+                    .build();
+        });
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
index 33b195c..bfdca0f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardTest.java
@@ -69,6 +69,7 @@
 
     @After
     public void tearDown() throws Exception {
+        mUiccCard.dispose();
         mUiccCard = null;
         mIccIoResult = null;
         super.tearDown();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
index 58a8153..3343570 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
@@ -15,8 +15,6 @@
  */
 package com.android.internal.telephony.uicc;
 
-import static junit.framework.Assert.fail;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -131,6 +129,7 @@
 
     @After
     public void tearDown() throws Exception {
+        if (mUiccControllerUT != null) mUiccControllerUT.dispose();
         mUiccControllerUT = null;
         super.tearDown();
     }
@@ -145,6 +144,7 @@
                 com.android.internal.R.array.non_removable_euicc_slots,
                 nonRemovableEuiccSlots);
         replaceInstance(UiccController.class, "mInstance", null, null);
+        mUiccControllerUT.dispose();
         mUiccControllerUT = UiccController.make(mContext, mFeatureFlags);
         processAllMessages();
     }
@@ -250,7 +250,7 @@
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(mMockCard).when(mMockSlot).getUiccCard();
         doReturn(mMockPort).when(mMockCard).getUiccPort(0);
         doReturn("A1B2C3D4").when(mMockPort).getIccId();
@@ -296,7 +296,7 @@
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(true).when(mMockSlot).isEuicc();
 
         // simulate slot status loaded so that the UiccController sets the card ID
@@ -323,7 +323,7 @@
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(true).when(mMockSlot).isEuicc();
 
         // simulate slot status loaded so that the UiccController sets the card ID
@@ -351,7 +351,7 @@
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(false).when(mMockSlot).isEuicc();
         doReturn(mMockCard).when(mMockSlot).getUiccCard();
         doReturn("ASDF1234").when(mMockCard).getCardId();
@@ -402,7 +402,7 @@
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(false).when(mMockSlot).isEuicc();
         doReturn(mMockCard).when(mMockSlot).getUiccCard();
         doReturn("ASDF1234").when(mMockCard).getCardId();
@@ -453,7 +453,7 @@
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(true).when(mMockSlot).isEuicc();
         doReturn(null).when(mMockSlot).getUiccCard();
         doReturn(false).when(mMockSlot).isRemovable();
@@ -499,21 +499,17 @@
      * The default eUICC should not be the removable slot if there is a built-in eUICC.
      */
     @Test
-    public void testDefaultEuiccIsNotRemovable() {
-        try {
-            reconfigureSlots(2, new int[]{ 1 } /* non-removable slot */);
-        } catch (Exception e) {
-            fail("Unable to reconfigure slots.");
-        }
+    public void testDefaultEuiccIsNotRemovable() throws Exception {
+        reconfigureSlots(2, new int[]{ 1 } /* non-removable slot */);
 
         // Give UiccController a real context so it can use shared preferences
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots so that [0] is a removable eUICC and [1] is built-in
-        mUiccControllerUT.mUiccSlots[0] = mMockRemovableEuiccSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockRemovableEuiccSlot);
         doReturn(true).when(mMockRemovableEuiccSlot).isEuicc();
         doReturn(true).when(mMockRemovableEuiccSlot).isRemovable();
-        mUiccControllerUT.mUiccSlots[1] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(1, mMockSlot);
         doReturn(true).when(mMockSlot).isEuicc();
         doReturn(false).when(mMockSlot).isRemovable();
 
@@ -550,21 +546,17 @@
      * not depend on the order of the slots.
      */
     @Test
-    public void testDefaultEuiccIsNotRemovable_swapSlotOrder() {
-        try {
-            reconfigureSlots(2, new int[]{ 0 } /* non-removable slot */);
-        } catch (Exception e) {
-            fail("Unable to reconfigure slots.");
-        }
+    public void testDefaultEuiccIsNotRemovable_swapSlotOrder() throws Exception {
+        reconfigureSlots(2, new int[]{ 0 } /* non-removable slot */);
 
         // Give UiccController a real context so it can use shared preferences
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots so that [0] is a built-in eUICC and [1] is removable
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(true).when(mMockSlot).isEuicc();
         doReturn(false).when(mMockSlot).isRemovable();
-        mUiccControllerUT.mUiccSlots[1] = mMockRemovableEuiccSlot;
+        mUiccControllerUT.setUiccSlot(1, mMockRemovableEuiccSlot);
         doReturn(true).when(mMockRemovableEuiccSlot).isEuicc();
         doReturn(true).when(mMockRemovableEuiccSlot).isRemovable();
 
@@ -603,21 +595,17 @@
      * the removable eUICC.
      */
     @Test
-    public void testDefaultEuiccIsNotRemovable_EuiccIsInactive() {
-        try {
-            reconfigureSlots(2, new int[]{ 1 } /* non-removable slot */);
-        } catch (Exception e) {
-            fail();
-        }
+    public void testDefaultEuiccIsNotRemovable_EuiccIsInactive() throws Exception {
+        reconfigureSlots(2, new int[]{ 1 } /* non-removable slot */);
 
         // Give UiccController a real context so it can use shared preferences
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots. Slot 0 is inactive here.
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(true).when(mMockSlot).isEuicc();
         doReturn(false).when(mMockSlot).isRemovable();
-        mUiccControllerUT.mUiccSlots[1] = mMockRemovableEuiccSlot;
+        mUiccControllerUT.setUiccSlot(1, mMockRemovableEuiccSlot);
         doReturn(true).when(mMockRemovableEuiccSlot).isEuicc();
         doReturn(true).when(mMockRemovableEuiccSlot).isRemovable();
 
@@ -669,7 +657,7 @@
         mUiccControllerUT.mContext = InstrumentationRegistry.getContext();
 
         // Mock out UiccSlots
-        mUiccControllerUT.mUiccSlots[0] = mMockSlot;
+        mUiccControllerUT.setUiccSlot(0, mMockSlot);
         doReturn(true).when(mMockSlot).isEuicc();
         doReturn(null).when(mMockSlot).getUiccCard();
         //doReturn("123451234567890").when(mMockSlot).getIccId();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java
index a2b42af..47b7c53 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java
@@ -82,6 +82,7 @@
 
     @After
     public void tearDown() throws Exception {
+        mUiccPort.dispose();
         mUiccPort = null;
         mIccIoResult = null;
         super.tearDown();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
index 671f273..8449ecc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccSlotTest.java
@@ -103,6 +103,7 @@
         mTestHandlerThread = null;
         mTestHandler.removeCallbacksAndMessages(null);
         mTestHandler = null;
+        mUiccSlot.dispose();
         mUiccSlot = null;
         super.tearDown();
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
index f88bc1e..c9b159c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
@@ -51,6 +51,8 @@
     private static final int CARD_COUNT = 1;
     private static final String PROVISIONING_PACKAGE_NAME = "test.provisioning.package";
 
+    private UiccCard mUiccCardToDispose;
+
     // Mocked classes
     private Resources mResources;
 
@@ -77,6 +79,9 @@
     @After
     public void tearDown() throws Exception {
         super.tearDown();
+
+        if (mUiccCardToDispose != null) mUiccCardToDispose.dispose();
+        mUiccCardToDispose = null;
     }
 
     @Test @SmallTest
@@ -99,7 +104,7 @@
         msg.what = integerArgumentCaptor.getValue();
 
         // The first broadcast should be sent after initialization.
-        UiccCard card = new UiccCard(mContext, mSimulatedCommands,
+        UiccCard card = mUiccCardToDispose = new UiccCard(mContext, mSimulatedCommands,
                 makeCardStatus(CardState.CARDSTATE_PRESENT), 0 /* phoneId */, new Object(),
                 IccSlotStatus.MultipleEnabledProfilesMode.NONE);
         when(UiccController.getInstance().getUiccCardForPhone(0)).thenReturn(card);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
index bcb5c4c..560c9aa 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccCardTest.java
@@ -109,7 +109,10 @@
             mHandler.removeCallbacksAndMessages(null);
             mHandler = null;
         }
-        mEuiccCard = null;
+        if (mEuiccCard != null) {
+            mEuiccCard.dispose();
+            mEuiccCard = null;
+        }
         super.tearDown();
     }
 
@@ -132,6 +135,7 @@
     @Test
     public void testPassEidInConstructor() {
         mMockIccCardStatus.eid = "1A2B3C4D";
+        mEuiccCard.dispose();
         mEuiccCard = new EuiccCard(mContextFixture.getTestDouble(), mMockCi,
                 mMockIccCardStatus, 0 /* phoneId */, new Object(),
                 IccSlotStatus.MultipleEnabledProfilesMode.NONE);
@@ -154,6 +158,7 @@
     public void testLoadEidAndNotifyRegistrants() {
         int channel = mockLogicalChannelResponses("BF3E065A041A2B3C4D9000");
         mHandler.post(() -> {
+            mEuiccCard.dispose();
             mEuiccCard = new EuiccCard(mContextFixture.getTestDouble(), mMockCi,
                     mMockIccCardStatus, 0 /* phoneId */, new Object(),
                     IccSlotStatus.MultipleEnabledProfilesMode.NONE);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java
index 2fef021..f0f1af3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/EuiccPortTest.java
@@ -141,6 +141,7 @@
     public void tearDown() throws Exception {
         mHandler.removeCallbacksAndMessages(null);
         mHandler = null;
+        mEuiccPort.dispose();
         mEuiccPort = null;
         super.tearDown();
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
index a65814e..1252ff8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/ApduSenderTest.java
@@ -16,36 +16,52 @@
 
 package com.android.internal.telephony.uicc.euicc.apdu;
 
+import static com.android.internal.telephony.CommandException.Error.RADIO_NOT_AVAILABLE;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.os.Handler;
 import android.os.Looper;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.preference.PreferenceManager;
+import android.telephony.IccOpenLogicalChannelResponse;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.InstrumentationRegistry;
+
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.euicc.EuiccSession;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.uicc.IccIoResult;
 import com.android.internal.telephony.uicc.IccUtils;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class ApduSenderTest {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     private static class ResponseCaptor extends ApduSenderResultCallback {
         public byte[] response;
@@ -75,6 +91,13 @@
         }
     }
 
+    private static final int PHONE_ID = 0;
+    private static final String SESSION_ID = "TEST";
+    // keep in sync with ApduSender.mChannelKey
+    private static final String SHARED_PREFS_KEY_CHANNEL_ID = "esim-channel_0";
+    // keep in sync with ApduSender.mChannelResponseKey
+    private static final String SHARED_PREFS_KEY_CHANNEL_RESPONSE = "esim-res-id_0";
+
     // Mocked classes
     private CommandsInterface mMockCi;
 
@@ -82,19 +105,20 @@
     private Handler mHandler;
     private ResponseCaptor mResponseCaptor;
     private byte[] mSelectResponse;
-    private static final String AID = "B2C3D4";
     private ApduSender mSender;
 
     @Before
     public void setUp() {
-        mMockCi = mock(CommandsInterface.class);
-        mHandler = new Handler(Looper.myLooper());
+        mSetFlagsRule.enableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER);
 
+        mMockCi = mock(CommandsInterface.class);
+        mLooper = TestableLooper.get(this);
+        mHandler = new Handler(mLooper.getLooper());
         mResponseCaptor = new ResponseCaptor();
         mSelectResponse = null;
 
-        mSender = new ApduSender(mMockCi, AID, false /* supportExtendedApdu */);
-        mLooper = TestableLooper.get(this);
+        mSender = new ApduSender(InstrumentationRegistry.getContext(), PHONE_ID,
+                            mMockCi, ApduSender.ISD_R_AID, false /* supportExtendedApdu */);
     }
 
     @After
@@ -105,6 +129,19 @@
         mResponseCaptor = null;
         mSelectResponse = null;
         mSender = null;
+
+        EuiccSession.get().endSession(SESSION_ID);
+        clearSharedPreferences();
+    }
+
+    @Test
+    public void testWrongAid_throwsIllegalArgumentException() {
+        String wrongAid = "-1";
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            new ApduSender(InstrumentationRegistry.getContext(), 0 /* phoneId= */,
+                            mMockCi, wrongAid, false /* supportExtendedApdu */);
+        });
     }
 
     @Test
@@ -119,7 +156,7 @@
         assertEquals("A1A1A19000", IccUtils.bytesToHexString(mSelectResponse));
         assertNull(mResponseCaptor.response);
         assertNull(mResponseCaptor.exception);
-        verify(mMockCi).iccOpenLogicalChannel(eq(AID), anyInt(), any());
+        verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
         verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
     }
 
@@ -135,7 +172,7 @@
         assertNull("Request provider should not be called when failed to open channel.",
                 mSelectResponse);
         assertTrue(mResponseCaptor.exception instanceof ApduException);
-        verify(mMockCi).iccOpenLogicalChannel(eq(AID), anyInt(), any());
+        verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
     }
 
     @Test
@@ -149,8 +186,11 @@
         mLooper.processAllMessages();
 
         assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response));
-        verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(0), eq("a"), anyBoolean(), any());
+        InOrder inOrder = inOrder(mMockCi);
+        inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+        inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
+                eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
+        inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
     }
 
     @Test
@@ -169,14 +209,17 @@
         mLooper.processAllMessages();
 
         assertEquals("A4", IccUtils.bytesToHexString(mResponseCaptor.response));
-        verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(0), eq("a"), anyBoolean(), any());
-        verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(1), eq("ab"), anyBoolean(), any());
-        verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10), eq(1), eq(2),
-                eq(3), eq(0), eq(""), anyBoolean(), any());
-        verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81), eq(0xE2), eq(0x91),
-                eq(0), eq(2), eq("abcd"), anyBoolean(), any());
+        InOrder inOrder = inOrder(mMockCi);
+        inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+        inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
+                eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
+        inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
+                eq(1), eq(2), eq(3), eq(1), eq("ab"), anyBoolean(), any());
+        inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
+                eq(1), eq(2),  eq(3), eq(0), eq(""), anyBoolean(), any());
+        inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(0x81),
+                eq(0xE2), eq(0x91), eq(0), eq(2), eq("abcd"), anyBoolean(), any());
+        inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
     }
 
     @Test
@@ -339,6 +382,158 @@
 
         assertNull("Should not open channel when another one is already opened.", mSelectResponse);
         assertTrue(mResponseCaptor.exception instanceof ApduException);
-        verify(mMockCi, times(1)).iccOpenLogicalChannel(eq(AID), anyInt(), any());
+        verify(mMockCi, times(1)).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+    }
+
+    @Test
+    public void testConstructor_doNotCloseOpenChannelInSharedPreference()
+                  throws InterruptedException {
+        // Open a channel and not close it, by making CI.iccTransmitApduLogicalChannel throw.
+        int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
+        doThrow(new RuntimeException()).when(mMockCi).iccTransmitApduLogicalChannel(
+                eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(),
+                anyBoolean(), any());
+        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+        mLooper.processAllMessages();
+        // Stub close channel
+        reset(mMockCi);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
+
+        // Call constructor
+        mSender = new ApduSender(InstrumentationRegistry.getContext(), PHONE_ID,
+                            mMockCi, ApduSender.ISD_R_AID, false /* supportExtendedApdu */);
+        mLooper.processAllMessages();
+
+        // The constructor should have closed channel
+        verify(mMockCi, times(0)).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
+        assertEquals(1, getChannelIdFromSharedPreferences());
+    }
+
+    @Test
+    public void testSend_OpenChannelFailedNoSuchElement_useChannelInSharedPreference() {
+        // Open a channel but not close, by making CI.iccTransmitApduLogicalChannel throw.
+        int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
+        doThrow(new RuntimeException()).when(mMockCi).iccTransmitApduLogicalChannel(
+                eq(channel), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(),
+                anyBoolean(), any());
+        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+        mLooper.processAllMessages();
+        reset(mMockCi);
+        // Constructor fails to close channel
+        LogicalChannelMocker.mockCloseLogicalChannel(
+                mMockCi, channel, new CommandException(RADIO_NOT_AVAILABLE));
+        mSender = new ApduSender(InstrumentationRegistry.getContext(), PHONE_ID,
+                            mMockCi, ApduSender.ISD_R_AID, false /* supportExtendedApdu */);
+        mLooper.processAllMessages();
+        reset(mMockCi);
+        // Stub open channel failure NO_SUCH_ELEMENT
+        LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi,
+                new CommandException(CommandException.Error.NO_SUCH_ELEMENT));
+        LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A1A1A19000");
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
+
+        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+        mLooper.processAllMessages();
+
+        // open channel would fail, and send/close would succeed because of
+        // previous open response saved in sharedPref
+        InOrder inOrder = inOrder(mMockCi);
+        inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+        inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel),
+                 eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
+        inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testSend_euiccSession_shouldNotCloseChannel()
+            throws InterruptedException {
+        int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
+        LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A1A1A19000");
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
+        EuiccSession.get().startSession(SESSION_ID);
+
+        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+        mLooper.processAllMessages();
+
+        assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response));
+        InOrder inOrder = inOrder(mMockCi);
+        inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+        inOrder.verify(mMockCi).iccTransmitApduLogicalChannel(eq(channel), eq(channel | 10),
+                eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
+        // No iccCloseLogicalChannel
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testSendTwice_euiccSession_shouldOpenChannelOnceNotCloseChannel()
+            throws InterruptedException {
+        int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
+        LogicalChannelMocker.mockSendToLogicalChannel(
+                    mMockCi, channel, "A1A1A19000", "A1A1A19000");
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
+        EuiccSession.get().startSession(SESSION_ID);
+
+        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+        mLooper.processAllMessages();
+        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+        mLooper.processAllMessages();
+
+        assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response));
+        InOrder inOrder = inOrder(mMockCi);
+        // iccOpenLogicalChannel once
+        inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+        // iccTransmitApduLogicalChannel twice
+        inOrder.verify(mMockCi, times(2)).iccTransmitApduLogicalChannel(eq(channel),
+                 eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
+        // No iccCloseLogicalChannel
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void testSendTwice_thenEndSession() throws InterruptedException {
+        int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
+        LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel,
+                "A1A1A19000", "A1A1A19000");
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
+        EuiccSession.get().startSession(SESSION_ID);
+
+        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+        mLooper.processAllMessages();
+        mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
+                10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
+        mLooper.processAllMessages();
+        EuiccSession.get().endSession(SESSION_ID);
+        mLooper.processAllMessages();
+
+        assertEquals("A1A1A1", IccUtils.bytesToHexString(mResponseCaptor.response));
+        InOrder inOrder = inOrder(mMockCi);
+        // iccOpenLogicalChannel once
+        inOrder.verify(mMockCi).iccOpenLogicalChannel(eq(ApduSender.ISD_R_AID), anyInt(), any());
+        // iccTransmitApduLogicalChannel twice
+        inOrder.verify(mMockCi, times(2)).iccTransmitApduLogicalChannel(eq(channel),
+                 eq(channel | 10), eq(1), eq(2), eq(3), eq(0), eq("a"), anyBoolean(), any());
+        // iccCloseLogicalChannel once
+        inOrder.verify(mMockCi).iccCloseLogicalChannel(eq(channel), eq(true /*isEs10*/), any());
+    }
+
+    private int getChannelIdFromSharedPreferences() {
+        return PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getContext())
+                .getInt(SHARED_PREFS_KEY_CHANNEL_ID, -1);
+    }
+
+    private void clearSharedPreferences() {
+        PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getContext())
+                .edit()
+                .remove(SHARED_PREFS_KEY_CHANNEL_ID)
+                .remove(SHARED_PREFS_KEY_CHANNEL_RESPONSE)
+                .apply();
     }
 }