Merge "Introduce Radio HAL version 2.3" into main am: 7ccac500d8 am: 4d98608651

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

Change-Id: Ic7dc4122ebb0b76b7cc679a522b3d23196549dac
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
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 c1dc7e7..906abfc 100644
--- a/flags/calling.aconfig
+++ b/flags/calling.aconfig
@@ -4,7 +4,6 @@
 # OWNER=breadley TARGET=24Q3
 flag {
   name: "simultaneous_calling_indications"
-  is_exported: true
   namespace: "telephony"
   description: "APIs that are used to notify simultaneous calling changes to other applications."
   bug: "297446980"
@@ -21,6 +20,17 @@
 
 # OWNER=stevestatia TARGET=24Q4
 flag {
+    name: "national_country_code_formatting_for_local_calls"
+    namespace: "telephony"
+    description: "Make requests and bug fixes for formatting local calls based on country codes easier with a more scalable solution."
+    bug: "293993310"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=stevestatia TARGET=24Q4
+flag {
     name: "remove_country_code_from_local_singapore_calls"
     namespace: "telephony"
     description: "Fix bug where the country code is being shown when merging in local Singapore numbers to conference calls."
@@ -29,3 +39,34 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+# OWNER=grantmenke TARGET=25Q1
+flag {
+    name: "remap_disconnect_cause_sip_request_cancelled"
+    namespace: "telephony"
+    description: "Fix dialer UI bug by remapping disconnect CODE_SIP_REQUEST_CANCELLED to DisconnectCause.NORMAL"
+    bug: "351258918"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+    is_exported: true
+}
+
+# OWNER=breadley TARGET=24Q4
+flag {
+    name: "delay_phone_account_registration"
+    namespace: "telephony"
+    description: "Fix bug where telephony would try to register for PhoneAccounts when Telecom isn't ready yet"
+    bug: "349731543"
+    metadata {
+        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"
+}
diff --git a/flags/carrier.aconfig b/flags/carrier.aconfig
new file mode 100644
index 0000000..265d258
--- /dev/null
+++ b/flags/carrier.aconfig
@@ -0,0 +1,23 @@
+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
+    }
+}
diff --git a/flags/data.aconfig b/flags/data.aconfig
index d956104..321ac6b 100644
--- a/flags/data.aconfig
+++ b/flags/data.aconfig
@@ -34,53 +34,6 @@
   }
 }
 
-# OWNER=linggm TARGET=24Q2
-flag {
-  name: "use_alarm_callback"
-  namespace: "telephony"
-  description: "Use alarm callback instead of broadcast."
-  bug: "311476875"
-}
-
-# OWNER=linggm TARGET=24Q2
-flag {
-  name: "refine_preferred_data_profile_selection"
-  namespace: "telephony"
-  description: "Upon internet network connect, refine selection of preferred data profile."
-  bug: "311476883"
-}
-
-# OWNER=linggm TARGET=24Q2
-flag {
-  name: "unthrottle_check_transport"
-  namespace: "telephony"
-  description: "Check transport when unthrottle."
-  bug: "303922311"
-}
-
-# OWNER=linggm TARGET=24Q1
-flag {
-  name: "relax_ho_teardown"
-  namespace: "telephony"
-  description: "Relax handover tear down if the device is currently in voice call."
-  bug: "270895912"
-}
-
-# OWNER=linggm TARGET=24Q2
-flag {
-  name: "allow_mmtel_in_non_vops"
-  namespace: "telephony"
-  description: "Allow bring up MMTEL in nonVops area specified by carrier config."
-  bug: "241198464"
-}
-
-# OWNER=jackyu TARGET=24Q2
-flag {
-  name: "metered_embb_urlcc"
-  namespace: "telephony"
-  description: "Force networks that have PRIORITIZE_BANDWIDTH or PRIORITIZE_LATENCY to be metered."
-  bug: "301310451"
-  }
 
 # OWNER=sarahchin TARGET=24Q3
 flag {
@@ -109,14 +62,6 @@
   bug:"286171724"
 }
 
-# OWNER=nagendranb TARGET=24Q2
-flag {
- name: "notify_data_activity_changed_with_slot"
-  namespace: "telephony"
-  description: "notify data activity changed for slot id"
-  bug: "309896936"
-}
-
 # OWNER=qingqi TARGET=24Q3
 flag {
   name: "vonr_enabled_metric"
@@ -125,14 +70,6 @@
   bug:"288449751"
 }
 
-# OWNER=willycwhu TARGET=24Q2
-flag {
-  name: "ignore_existing_networks_for_internet_allowed_checking"
-  namespace: "telephony"
-  description: "Ignore existing networks when checking if internet is allowed"
-  bug: "284420611"
-}
-
 # OWNER=apsankar TARGET=24Q3
 flag {
   name: "data_call_session_stats_captures_cross_sim_calling"
@@ -141,14 +78,6 @@
   bug: "313956117"
 }
 
-# OWNER=jackyu TARGET=24Q2
-flag {
-  name: "force_iwlan_mms"
-  namespace: "telephony"
-  description: "When QNS prefers MMS on IWLAN, MMS will be attempted on IWLAN if it can, even though if existing cellular network already supports MMS."
-  bug: "316211526"
-}
-
 # OWNER=sewook TARGET=24Q3
 flag {
   name: "reconnect_qualified_network"
@@ -172,3 +101,44 @@
   description: "Write DataRatStateChanged atom"
   bug:"318519337"
 }
+
+# OWNER=jackyu TARGET=25Q1
+flag {
+  name: "dds_callback"
+  namespace: "telephony"
+  description: "Adding new callback when DDS changed"
+  bug:"353723350"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+# OWNER=jackyu TARGET=25Q1
+flag {
+  name: "support_network_provider"
+  namespace: "telephony"
+  description: "Deprecate network factory and adapt the new network provider model from connectivity service"
+  bug: "343370895"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+# OWNER=jackyu TARGET=25Q1
+flag {
+  name: "sim_disabled_graceful_tear_down"
+  namespace: "telephony"
+  description: "Gracefully tear down the networks when SIM is disabled."
+  bug: "362372940"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+# OWNER=TBD TARGET=TBD
+flag {
+  name: "oem_paid_private"
+  namespace: "telephony"
+  description: "Support OEM_PAID and OEM_PRIVATE networks"
+  bug: "366194627"
+}
diff --git a/flags/domainselection.aconfig b/flags/domainselection.aconfig
index 623c3b6..4b1ad4a 100644
--- a/flags/domainselection.aconfig
+++ b/flags/domainselection.aconfig
@@ -33,3 +33,11 @@
     description: "This flag controls domain selection metrics."
     bug:"258112541"
 }
+
+# OWNER=jdyou TARGET=24Q4
+flag {
+    name: "hangup_emergency_call_for_cross_sim_redialing"
+    namespace: "telephony"
+    description: "This flag controls the behavior of terminating an emergency call for cross SIM redialing."
+    bug:"336398541"
+}
diff --git a/flags/ims.aconfig b/flags/ims.aconfig
index 4eff505..4426933 100644
--- a/flags/ims.aconfig
+++ b/flags/ims.aconfig
@@ -115,3 +115,33 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+# OWNER=joonhunshin TARGET=24Q4
+flag {
+    name: "prevent_hangup_during_call_merge"
+    namespace: "telephony"
+    description: "This flag prevents hangup call during call merge"
+    bug:"317070933"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=joonhunshin TARGET=24Q4
+flag {
+    name: "avoid_deleting_ims_object_from_cache"
+    namespace: "telephony"
+    description: "This flag controls deleting cached object to synchronize part of application callback and part of ImsFeature behavior"
+    bug:"353577279"
+    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"
+}
diff --git a/flags/messaging.aconfig b/flags/messaging.aconfig
index 1030ba7..905dc94 100644
--- a/flags/messaging.aconfig
+++ b/flags/messaging.aconfig
@@ -38,4 +38,12 @@
     metadata {
         purpose: PURPOSE_BUGFIX
     }
+}
+
+# OWNER=nykkumar TARGET=25Q2
+flag {
+  name: "sms_mms_deliver_broadcasts_redirect_to_main_user"
+  namespace: "telephony"
+  description: "This flag controls the redirection of SMS_DELIVER AND WAP_PUSH_DELIVER broadcasts to the MAIN user."
+  bug: "314321617"
 }
\ No newline at end of file
diff --git a/flags/misc.aconfig b/flags/misc.aconfig
index 862aa33..2d6992a 100644
--- a/flags/misc.aconfig
+++ b/flags/misc.aconfig
@@ -78,46 +78,6 @@
   bug: "309896524"
 }
 
-# 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"
-    description: "Allows applications to launch Reset Mobile Network Settings page in Settings app."
-    bug:"271921464"
-}
-
-# OWNER=rambowang TARGET=24Q3
-flag {
-    name: "add_anomaly_when_notify_config_changed_with_invalid_phone"
-    namespace: "telephony"
-    description: "Report anomaly when CarrierConfigLoader received config change with sub that maps to invalid phoneId"
-    bug:"270757342"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
-}
-
-# OWNER=rambowang TARGET=24Q3
-flag {
-    name: "hide_preinstalled_carrier_app_at_most_once"
-    namespace: "telephony"
-    description: "Fix bug when preloaded carrier app is uninstalled and lose provisioning data"
-    bug:"158028151"
-    metadata {
-        purpose: PURPOSE_BUGFIX
-    }
-}
-
 # OWNER=sangyun TARGET=24Q3
 flag {
     name: "roaming_notification_for_single_data_network"
@@ -206,3 +166,48 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+# OWNER=joonhunshin TARGET=24Q4
+flag {
+    name: "use_carrier_config_for_cfnry_time_via_mmi"
+    namespace: "telephony"
+    description: "This flag allows the no reply timer to be referenced in the carrier config when setting up call forward via MMI code and there is no timer value."
+    bug:"342346827"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=jackyu TARGET=25Q2
+flag {
+    name: "hsum_broadcast"
+    namespace: "telephony"
+    description: "Fixed the bug that broadcast intent is only sent to the system user."
+    bug:"362554272"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=rambowang TARGET=25Q2
+flag {
+    name: "support_carrier_services_for_hsum"
+    namespace: "telephony"
+    description: "Support Carrier Services (APIs) for HSUM."
+    bug:"345522246"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=jackyu TARGET=25Q2
+flag {
+    name: "hsum_package_manager"
+    namespace: "telephony"
+    description: "Fixed the bug that package manager is not for the right user"
+    bug:"356827794"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
diff --git a/flags/satellite.aconfig b/flags/satellite.aconfig
index 2eea80a..825ea78 100644
--- a/flags/satellite.aconfig
+++ b/flags/satellite.aconfig
@@ -33,4 +33,40 @@
     namespace: "telephony"
     description: "This flag enables satellite persistent logging"
     bug:"339877723"
+}
+
+# OWNER=hyosunkim TARGET=24Q3
+flag {
+    name: "carrier_roaming_nb_iot_ntn"
+    namespace: "telephony"
+    description: "This flag enables satellite carrier roaming to nb iot ntn."
+    bug:"348253735"
+}
+
+# OWNER=tnd TARGET=24Q4
+flag {
+    name: "oem_enabled_satellite_phase_2"
+    is_exported: true
+    namespace: "telephony"
+    description: "This flag controls satellite communication supported by OEMs in phase 2."
+    bug:"349624547"
+}
+
+# OWNER=youngtaecha TARGET=24Q4
+flag {
+  name: "geofence_enhancement_for_better_ux"
+  namespace: "telephony"
+  description: "Enhance geofence to improve UX experience"
+  bug: "347711329"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+# 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"
 }
\ No newline at end of file
diff --git a/flags/subscription.aconfig b/flags/subscription.aconfig
index 9a5dabc..543aa20 100644
--- a/flags/subscription.aconfig
+++ b/flags/subscription.aconfig
@@ -19,23 +19,6 @@
   bug: "296076674"
 }
 
-# OWNER=rambowang TARGET=24Q3
-flag {
-  name: "data_only_cellular_service"
-  is_exported: true
-  namespace: "telephony"
-  description: "Supports customized cellular service capabilities per subscription."
-  bug: "296097429"
-}
-
-# OWNER=rambowang TARGET=24Q3
-flag {
-  name: "data_only_service_allow_emergency_call_only"
-  namespace: "telephony"
-  description: "Support emergency call only for data only cellular service."
-  bug: "296097429"
-}
-
 # OWNER=hhshin TARGET=24Q3
 flag {
   name: "support_psim_to_esim_conversion"
@@ -77,3 +60,15 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+# OWNER=jackyu TARGET=24Q4
+flag {
+  name: "uicc_phone_number_fix"
+  namespace: "telephony"
+  description: "Fixed that empty phone number when getLine1Number returns empty"
+  bug: "302437869"
+
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/flags/uicc.aconfig b/flags/uicc.aconfig
index 2679cfe..ad0c59f 100644
--- a/flags/uicc.aconfig
+++ b/flags/uicc.aconfig
@@ -43,14 +43,6 @@
     bug:"318348580"
 }
 
-# OWNER=rambowang TARGET=24Q3
-flag {
-    name: "cleanup_open_logical_channel_record_on_dispose"
-    namespace: "telephony"
-    description: "This flag cleans up the OpenLogicalChannelRecord once SIM is removed"
-    bug:"335046531"
-}
-
 # OWNER=arunvoddu TARGET=24Q4
 flag {
     name: "set_carrier_restriction_status"
@@ -58,3 +50,33 @@
     description: "This flag controls the visibility of the setCarrierRestrictionStatus API in carrierRestrictionRules class."
     bug:"342411308"
 }
+
+# OWNER=arunvoddu TARGET=24Q4
+flag {
+    name: "uicc_app_count_check_to_create_channel"
+    namespace: "telephony"
+    description: "This flag controls to create the open channel when uicc application count is greater than 0."
+    bug:"349966950"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=mewan TARGET=24Q4
+flag {
+    name: "optimization_apdu_sender"
+    namespace: "telephony"
+    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
+    }
+}
\ No newline at end of file
diff --git a/proto/Android.bp b/proto/Android.bp
index 15c0aea..adc9b5e 100644
--- a/proto/Android.bp
+++ b/proto/Android.bp
@@ -31,3 +31,12 @@
     // Pin java_version until jarjar is certified to support later versions. http://b/72703434
     java_version: "1.8",
 }
+
+java_library_static {
+    name: "telephony-config-update-proto-lite",
+    proto: {
+        type: "lite",
+    },
+    srcs: ["src/**/telephony_config_update.proto"],
+    host_supported: true,
+}
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto
index 48e7b0d..9dbdcb0 100644
--- a/proto/src/persist_atoms.proto
+++ b/proto/src/persist_atoms.proto
@@ -320,6 +320,7 @@
     optional bool is_ntn = 41;
     optional bool supports_business_call_composer = 42;
     optional int32 call_composer_status = 43;
+    optional int32 precise_call_state_on_setup = 44;
 
     // Internal use only
     optional int64 setup_begin_millis = 10001;
@@ -717,6 +718,11 @@
     optional int32 count_of_allowed_satellite_access = 26;
     optional int32 count_of_disallowed_satellite_access = 27;
     optional int32 count_of_satellite_access_check_fail = 28;
+    optional bool is_provisioned = 29;
+    optional int32 carrier_id = 30;
+    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;
 }
 
 message SatelliteSession {
@@ -733,6 +739,10 @@
     optional int32 count_of_incoming_datagram_failed = 11;
     optional bool is_demo_mode = 12;
     optional int32 max_ntn_signal_strength_level = 13;
+    optional int32 carrier_id = 14;
+    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;
 }
 
 message SatelliteIncomingDatagram {
@@ -740,6 +750,7 @@
     optional int32 datagram_size_bytes = 2;
     optional int64 datagram_transfer_time_millis = 3;
     optional bool is_demo_mode = 4;
+    optional int32 carrier_id = 5;
 }
 
 message SatelliteOutgoingDatagram {
@@ -748,6 +759,7 @@
     optional int32 datagram_size_bytes = 3;
     optional int64 datagram_transfer_time_millis = 4;
     optional bool is_demo_mode = 5;
+    optional int32 carrier_id = 6;
 }
 
 message SatelliteProvision {
@@ -755,6 +767,7 @@
     optional int32 provisioning_time_sec = 2;
     optional bool is_provision_request = 3;
     optional bool is_canceled = 4;
+    optional int32 carrier_id = 5;
 }
 
 message SatelliteSosMessageRecommender {
@@ -766,6 +779,8 @@
     optional bool is_multi_sim = 6;
     optional int32 recommending_handover_type = 7;
     optional bool is_satellite_allowed_in_current_location = 8;
+    optional bool is_wifi_connected = 9;
+    optional int32 carrier_id = 10;
 }
 
 message DataNetworkValidation {
@@ -805,6 +820,8 @@
     optional int32 satellite_session_gap_min_sec = 5;
     optional int32 satellite_session_gap_avg_sec = 6;
     optional int32 satellite_session_gap_max_sec = 7;
+    optional int32 carrier_id = 8;
+    optional bool is_device_entitled = 9;
 }
 
 message SatelliteEntitlement {
@@ -832,4 +849,6 @@
     optional int32 result_code = 7;
     repeated string country_codes = 8;
     optional int32 config_data_source = 9;
+    optional int32 carrier_id = 10;
+    optional int32 triggering_event = 11;
 }
diff --git a/src/java/com/android/internal/telephony/CarrierActionAgent.java b/src/java/com/android/internal/telephony/CarrierActionAgent.java
index 6d74c18..c4ba77d 100644
--- a/src/java/com/android/internal/telephony/CarrierActionAgent.java
+++ b/src/java/com/android/internal/telephony/CarrierActionAgent.java
@@ -186,7 +186,8 @@
                         mPhone.getServiceStateTracker().registerForDataRoamingOff(
                                 this, EVENT_DATA_ROAMING_OFF, null, false);
                     }
-                } else if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(iccState)) {
+                } else if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(iccState)
+                        || IccCardConstants.INTENT_VALUE_ICC_NOT_READY.equals(iccState)) {
                     log("EVENT_SIM_STATE_CHANGED status: " + iccState);
                     carrierActionReset();
                     mSettingsObserver.unobserve();
@@ -257,6 +258,8 @@
                 return mCarrierActionOnRadioEnabled;
             case CARRIER_ACTION_REPORT_DEFAULT_NETWORK_STATUS:
                 return mCarrierActionReportDefaultNetworkStatus;
+            case EVENT_APN_SETTINGS_CHANGED:
+                return null;  // we don't know if it's enabled, but this is not "unsupported" action
             default:
                 loge("Unsupported action: " + action);
                 return null;
diff --git a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
index 10a3a32..1667b7d 100644
--- a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -120,7 +120,7 @@
     private boolean mAllowedOverMeteredNetwork = false;
     private boolean mDeleteOldKeyAfterDownload = false;
     private boolean mIsRequiredToHandleUnlock;
-    private final TelephonyManager mTelephonyManager;
+    private TelephonyManager mTelephonyManager;
     private UserManager mUserManager;
     @VisibleForTesting
     public String mMccMncForDownload = "";
@@ -146,13 +146,14 @@
                 .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
-        carrierConfigManager.registerCarrierConfigChangeListener(this::post,
+        if (carrierConfigManager != null) {
+            carrierConfigManager.registerCarrierConfigChangeListener(this::post,
                 (slotIndex, subId, carrierId, specificCarrierId) -> {
                     if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
                         logd("CarrierConfig changed slotIndex = " + slotIndex + " subId = " + subId
@@ -162,17 +163,36 @@
                         if ((slotIndex == mPhone.getPhoneId()) && (carrierId > 0
                                 || !TextUtils.isEmpty(
                                 mMccMncForDownload))) {
-                            mCarrierId = carrierId;
+                            if (mTelephonyManager == null
+                                    || mTelephonyManager.getSubscriptionId() != subId) {
+                                logd("recreating TelManager with SubId = " + subId);
+                                mTelephonyManager = mContext.getSystemService(
+                                                TelephonyManager.class)
+                                        .createForSubscriptionId(subId);
+                            }
+                            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();
@@ -187,9 +207,15 @@
                         }
                     }
                 });
+        }
         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
@@ -298,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);
                         }
@@ -529,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();
     }
@@ -787,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 67be1b6..6326d6c 100644
--- a/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
+++ b/src/java/com/android/internal/telephony/CarrierPrivilegesTracker.java
@@ -31,6 +31,7 @@
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -45,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;
@@ -66,7 +68,7 @@
 import android.util.Pair;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.telephony.uicc.IccUtils;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.uicc.UiccPort;
 import com.android.internal.telephony.uicc.UiccProfile;
 import com.android.telephony.Rlog;
@@ -78,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;
@@ -93,6 +94,7 @@
  * Registered Telephony entities will receive notifications when the UIDs with these privileges
  * change.
  */
+@SuppressLint("MissingPermission")
 public class CarrierPrivilegesTracker extends Handler {
     private static final String TAG = CarrierPrivilegesTracker.class.getSimpleName();
 
@@ -176,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;
@@ -195,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<>();
 
@@ -223,6 +227,8 @@
             "mPrivilegedPackageInfoLock.writeLock()"})
     private boolean mSimIsReadyButNotLoaded = false;
 
+    private volatile Handler mCurrentHandler;
+
     /** Small snapshot to hold package names and UIDs of privileged packages. */
     private static final class PrivilegedPackageInfo {
         @NonNull final Set<String> mPackageNames;
@@ -297,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
@@ -316,7 +324,7 @@
                             boolean notExist = false;
                             try {
                                 disabledByUser = action.equals(Intent.ACTION_PACKAGE_CHANGED)
-                                        && mPackageManager.getApplicationEnabledSetting(pkgName)
+                                        && getApplicationEnabledSetting(pkgName)
                                         == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
                             } catch (IllegalArgumentException iae) {
                                 // Very rare case when package changed race with package removed
@@ -329,7 +337,8 @@
                                     ? ACTION_PACKAGE_REMOVED_OR_DISABLED_BY_USER
                                     : ACTION_PACKAGE_ADDED_REPLACED_OR_CHANGED;
 
-                            sendMessage(obtainMessage(what, pkgName));
+                            mCurrentHandler.sendMessage(
+                                    mCurrentHandler.obtainMessage(what, pkgName));
                             break;
                         }
                     }
@@ -337,27 +346,87 @@
             };
 
     public CarrierPrivilegesTracker(
-            @NonNull Looper looper, @NonNull Phone phone, @NonNull Context context) {
+            @NonNull Looper looper, @NonNull Phone phone,
+            @NonNull Context context, @NonNull FeatureFlags featureFlags) {
         super(looper);
-        mContext = context;
         mPhone = phone;
+        mContext = context;
+        mFeatureFlags = featureFlags;
         mPackageManager = mContext.getPackageManager();
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mCarrierConfigManager =
                 (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
         // Callback is executed in handler thread and directly handles carrier config update
-        mCarrierConfigManager.registerCarrierConfigChangeListener(this::post,
-                (slotIndex, subId, carrierId, specificCarrierId) -> handleCarrierConfigUpdated(
-                        subId, slotIndex));
+        if (mCarrierConfigManager != null) {
+            mCarrierConfigManager.registerCarrierConfigChangeListener(this::post,
+                    (slotIndex, subId, carrierId, specificCarrierId) -> handleCarrierConfigUpdated(
+                            subId, slotIndex));
+        }
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mTelephonyRegistryManager =
                 (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);
-        mContext.registerReceiver(mIntentReceiver, certFilter);
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            mContext.registerReceiverAsUser(
+                    mIntentReceiver, UserHandle.of(ActivityManager.getCurrentUser()), certFilter,
+                    /* broadcastPermission= */ null, /* scheduler= */ null);
+        } else {
+            mContext.registerReceiver(mIntentReceiver, certFilter);
+        }
 
         IntentFilter packageFilter = new IntentFilter();
         packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
@@ -368,9 +437,14 @@
         // For package-related broadcasts, specify the data scheme for "package" to receive the
         // package name along with the broadcast
         packageFilter.addDataScheme("package");
-        mContext.registerReceiver(mIntentReceiver, packageFilter);
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            mContext.registerReceiverAsUser(
+                    mIntentReceiver, UserHandle.of(ActivityManager.getCurrentUser()), packageFilter,
+                    /* broadcastPermission= */ null, /* scheduler= */ null);
+        } else {
+            mContext.registerReceiver(mIntentReceiver, packageFilter);
+        }
 
-        sendMessage(obtainMessage(ACTION_INITIALIZE_TRACKER));
     }
 
     @Override
@@ -444,7 +518,8 @@
                 CarrierConfigManager.getCarrierConfigSubset(
                         mContext, subId, KEY_CARRIER_CERTIFICATE_STRING_ARRAY);
         // CarrierConfigManager#isConfigForIdentifiedCarrier can handle null or empty bundle
-        if (!mCarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfigs)) {
+        if (mCarrierConfigManager == null
+                || !mCarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfigs)) {
             return Collections.EMPTY_LIST;
         }
 
@@ -477,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(
@@ -534,18 +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 = 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.
@@ -553,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;
         }
@@ -608,7 +710,7 @@
             msg +=
                     " installed pkgs="
                             + getObfuscatedPackages(
-                                    mInstalledPackageCerts.entrySet(),
+                                    mInstalledPackageCertHashes.entrySet(),
                                     e -> "pkg(" + Rlog.pii(TAG, e.getKey()) + ")=" + e.getValue());
         }
         mLocalLog.log(msg);
@@ -617,9 +719,12 @@
     private void refreshInstalledPackageCache() {
         List<PackageInfo> installedPackages =
                 mPackageManager.getInstalledPackagesAsUser(
-                        INSTALLED_PACKAGES_QUERY_FLAGS, UserHandle.SYSTEM.getIdentifier());
+                        INSTALLED_PACKAGES_QUERY_FLAGS,
+                        mFeatureFlags.supportCarrierServicesForHsum()
+                                ? 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);
@@ -708,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
@@ -728,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;
@@ -791,7 +926,9 @@
     private int getPackageUid(@Nullable String pkgName) {
         int uid = Process.INVALID_UID;
         try {
-            uid = mPackageManager.getPackageUid(pkgName, /* flags= */0);
+            uid = mFeatureFlags.supportCarrierServicesForHsum()
+                    ? mPackageManager.getPackageUidAsUser(pkgName, ActivityManager.getCurrentUser())
+                    : mPackageManager.getPackageUid(pkgName, /* flags= */0);
         } catch (NameNotFoundException e) {
             Rlog.e(TAG, "Unable to find uid for package " + pkgName);
         }
@@ -822,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);
@@ -838,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));
     }
 
     /**
@@ -854,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));
     }
 
@@ -991,10 +1129,25 @@
         // Do the PackageManager queries before we take the lock, as these are the longest-running
         // pieces of this method and don't depend on the set of carrier apps.
         List<ResolveInfo> resolveInfos = new ArrayList<>();
-        resolveInfos.addAll(mPackageManager.queryBroadcastReceivers(intent, 0));
-        resolveInfos.addAll(mPackageManager.queryIntentActivities(intent, 0));
-        resolveInfos.addAll(mPackageManager.queryIntentServices(intent, 0));
-        resolveInfos.addAll(mPackageManager.queryIntentContentProviders(intent, 0));
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            resolveInfos.addAll(
+                    mPackageManager.queryBroadcastReceiversAsUser(
+                            intent, /* flags= */ 0, ActivityManager.getCurrentUser()));
+            resolveInfos.addAll(
+                    mPackageManager.queryIntentActivitiesAsUser(
+                            intent, /* flags= */ 0, ActivityManager.getCurrentUser()));
+            resolveInfos.addAll(
+                    mPackageManager.queryIntentServicesAsUser(
+                            intent, /* flags= */ 0, ActivityManager.getCurrentUser()));
+            resolveInfos.addAll(
+                    mPackageManager.queryIntentContentProvidersAsUser(
+                            intent, /* flags= */ 0, ActivityManager.getCurrentUser()));
+        } else {
+            resolveInfos.addAll(mPackageManager.queryBroadcastReceivers(intent, 0));
+            resolveInfos.addAll(mPackageManager.queryIntentActivities(intent, 0));
+            resolveInfos.addAll(mPackageManager.queryIntentServices(intent, 0));
+            resolveInfos.addAll(mPackageManager.queryIntentContentProviders(intent, 0));
+        }
 
         // Now actually check which of the resolved packages have carrier privileges.
         mPrivilegedPackageInfoLock.readLock().lock();
@@ -1028,8 +1181,15 @@
 
     @NonNull
     private Pair<String, Integer> getCarrierService(@NonNull Set<String> simPrivilegedPackages) {
-        List<ResolveInfo> carrierServiceResolveInfos = mPackageManager.queryIntentServices(
-                new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), /* flags= */ 0);
+        List<ResolveInfo> carrierServiceResolveInfos =
+                mFeatureFlags.supportCarrierServicesForHsum()
+                        ? mPackageManager.queryIntentServicesAsUser(
+                                new Intent(CarrierService.CARRIER_SERVICE_INTERFACE),
+                                /* flags= */ 0,
+                                ActivityManager.getCurrentUser())
+                        : mPackageManager.queryIntentServices(
+                                new Intent(CarrierService.CARRIER_SERVICE_INTERFACE),
+                                /* flags= */ 0);
         String carrierServicePackageName = null;
         for (ResolveInfo resolveInfo : carrierServiceResolveInfos) {
             String packageName = getPackageName(resolveInfo);
@@ -1047,4 +1207,16 @@
                 ? new Pair<>(null, Process.INVALID_UID)
                 : new Pair<>(carrierServicePackageName, getPackageUid(carrierServicePackageName));
     }
+
+    private @PackageManager.EnabledState int getApplicationEnabledSetting(
+            @NonNull String packageName) {
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            return mContext.createContextAsUser(
+                            UserHandle.of(ActivityManager.getCurrentUser()), /* flags= */ 0)
+                    .getPackageManager()
+                    .getApplicationEnabledSetting(packageName);
+        } else {
+            return mPackageManager.getApplicationEnabledSetting(packageName);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/CarrierResolver.java b/src/java/com/android/internal/telephony/CarrierResolver.java
index 8a9b3e3..829bf6c 100644
--- a/src/java/com/android/internal/telephony/CarrierResolver.java
+++ b/src/java/com/android/internal/telephony/CarrierResolver.java
@@ -30,6 +30,7 @@
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Message;
+import android.os.UserHandle;
 import android.provider.Telephony;
 import android.service.carrier.CarrierIdentifier;
 import android.telephony.CarrierConfigManager;
@@ -41,6 +42,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.CarrierIdMatchStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
@@ -112,6 +114,9 @@
     private final LocalLog mCarrierIdLocalLog = new LocalLog(16);
     private final TelephonyManager mTelephonyMgr;
 
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
+
     private final ContentObserver mContentObserver = new ContentObserver(this) {
         @Override
         public void onChange(boolean selfChange, Uri uri) {
@@ -172,9 +177,10 @@
         }
     };
 
-    public CarrierResolver(Phone phone) {
+    public CarrierResolver(Phone phone, @NonNull FeatureFlags flags) {
         logd("Creating CarrierResolver[" + phone.getPhoneId() + "]");
         mContext = phone.getContext();
+        mFeatureFlags = flags;
         mPhone = phone;
         mTelephonyMgr = TelephonyManager.from(mContext);
 
@@ -500,7 +506,11 @@
             intent.putExtra(TelephonyManager.EXTRA_SPECIFIC_CARRIER_ID, mSpecificCarrierId);
             intent.putExtra(TelephonyManager.EXTRA_SPECIFIC_CARRIER_NAME, mSpecificCarrierName);
             intent.putExtra(TelephonyManager.EXTRA_SUBSCRIPTION_ID, mPhone.getSubId());
-            mContext.sendBroadcast(intent);
+            if (mFeatureFlags.hsumBroadcast()) {
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            } else {
+                mContext.sendBroadcast(intent);
+            }
 
             // notify content observers for specific carrier id change event.
             ContentValues cv = new ContentValues();
@@ -535,7 +545,11 @@
             intent.putExtra(TelephonyManager.EXTRA_CARRIER_ID, mCarrierId);
             intent.putExtra(TelephonyManager.EXTRA_CARRIER_NAME, mCarrierName);
             intent.putExtra(TelephonyManager.EXTRA_SUBSCRIPTION_ID, mPhone.getSubId());
-            mContext.sendBroadcast(intent);
+            if (mFeatureFlags.hsumBroadcast()) {
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            } else {
+                mContext.sendBroadcast(intent);
+            }
 
             // notify content observers for carrier id change event
             ContentValues cv = new ContentValues();
diff --git a/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
index 960d794..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;
@@ -45,6 +46,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.io.FileDescriptor;
@@ -160,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();
 
@@ -171,7 +177,7 @@
                 context, mHandler.getLooper(), UserHandle.ALL);
         try {
             Context contextAsUser = mContext.createPackageContextAsUser(mContext.getPackageName(),
-                0, UserHandle.SYSTEM);
+                0, Flags.supportCarrierServicesForHsum() ? UserHandle.CURRENT : UserHandle.SYSTEM);
             contextAsUser.registerReceiver(mUserUnlockedReceiver,
                 new IntentFilter(Intent.ACTION_USER_UNLOCKED), null /* broadcastPermission */,
                 mHandler);
diff --git a/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java b/src/java/com/android/internal/telephony/CarrierServiceStateTracker.java
index 6b99b56..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
@@ -116,7 +117,8 @@
         mTelephonyManager = mPhone.getContext().getSystemService(
                 TelephonyManager.class).createForSubscriptionId(mPhone.getSubId());
         CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
-        ccm.registerCarrierConfigChangeListener(
+        if (ccm != null) {
+            ccm.registerCarrierConfigChangeListener(
                 mPhone.getContext().getMainExecutor(),
                 (slotIndex, subId, carrierId, specificCarrierId) -> {
                     if (slotIndex != mPhone.getPhoneId()) return;
@@ -143,6 +145,7 @@
                     }
                     handleConfigChanges();
                 });
+        }
 
         // Listen for subscriber changes
         SubscriptionManager.from(mPhone.getContext()).addOnSubscriptionsChangedListener(
@@ -731,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();
@@ -741,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/CarrierSignalAgent.java b/src/java/com/android/internal/telephony/CarrierSignalAgent.java
index 3250cef..2abe643 100644
--- a/src/java/com/android/internal/telephony/CarrierSignalAgent.java
+++ b/src/java/com/android/internal/telephony/CarrierSignalAgent.java
@@ -130,13 +130,15 @@
         CarrierConfigManager carrierConfigManager = mPhone.getContext().getSystemService(
                 CarrierConfigManager.class);
         loadCarrierConfig();
-        carrierConfigManager.registerCarrierConfigChangeListener(
-                mPhone.getContext().getMainExecutor(),
-                (slotIndex, subId, carrierId, specificCarrierId) -> {
-                    if (slotIndex == mPhone.getPhoneId()) {
-                        loadCarrierConfig();
-                    }
-                });
+        if (carrierConfigManager != null) {
+            carrierConfigManager.registerCarrierConfigChangeListener(
+                    mPhone.getContext().getMainExecutor(),
+                    (slotIndex, subId, carrierId, specificCarrierId) -> {
+                        if (slotIndex == mPhone.getPhoneId()) {
+                            loadCarrierConfig();
+                        }
+                    });
+        }
         mPhone.getCarrierActionAgent().registerForCarrierAction(
                 CarrierActionAgent.CARRIER_ACTION_REPORT_DEFAULT_NETWORK_STATUS, this,
                 EVENT_REGISTER_DEFAULT_NETWORK_AVAIL, null, false);
diff --git a/src/java/com/android/internal/telephony/Connection.java b/src/java/com/android/internal/telephony/Connection.java
index 82b4c2b8e..7a1c8fe 100644
--- a/src/java/com/android/internal/telephony/Connection.java
+++ b/src/java/com/android/internal/telephony/Connection.java
@@ -692,8 +692,18 @@
                     && mEmergencyNumberInfo.getEmergencyCallRouting()
                         != EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY) {
                 int eccCategory = dialArgs.intentExtras.getInt(
-                        PhoneConstants.EXTRA_EMERGENCY_SERVICE_CATEGORY,
-                        mEmergencyNumberInfo.getEmergencyServiceCategoryBitmask());
+                    PhoneConstants.EXTRA_EMERGENCY_SERVICE_CATEGORY,
+                    mEmergencyNumberInfo.getEmergencyServiceCategoryBitmask());
+                // According to 3gpp 23.167 section 7.1.2, when CS domain is selected,
+                // emergency routing is performed only if the emergency category is provided.
+                if (this instanceof GsmCdmaConnection
+                        && dialArgs.intentExtras.getInt(
+                                PhoneConstants.EXTRA_EMERGENCY_SERVICE_CATEGORY,
+                                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED)
+                                == EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED) {
+                    Rlog.d(TAG, "setEmergencyCallInfo: specific eccCategory is required");
+                    return;
+                }
                 Rlog.d(TAG, "setEmergencyCallInfo: enforce emergency routing eccCategory="
                         + eccCategory);
                 List<String> emergencyUrns = dialArgs.intentExtras.getStringArrayList(
@@ -706,13 +716,32 @@
                         mEmergencyNumberInfo.getMnc(),
                         eccCategory,
                         emergencyUrns,
-                        mEmergencyNumberInfo.getEmergencyNumberSourceBitmask(),
+                        getEmergencyNumberSourceForEmergencyRouting(),
                         EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
             }
         }
     }
 
     /**
+     * Get the emergency number source to be used for emergency routing calls.
+     * This is not getting actual source, instead its forcing the source to
+     * EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING.
+     * Even when the source is EMERGENCY_NUMBER_SOURCE_DATABASE,
+     * to allow the category information delivered by the network to be used,
+     * the source is set to EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING.
+     */
+    private int getEmergencyNumberSourceForEmergencyRouting() {
+        int source = mEmergencyNumberInfo.getEmergencyNumberSourceBitmask();
+        Rlog.d(TAG, "getEmergencyNumberSourceForEmergencyRouting: source=" + source);
+
+        if (source != EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST) {
+            source = EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING;
+        }
+
+        return source;
+    }
+
+    /**
      * Set the non-detectable emergency number information.
      */
     public void setNonDetectableEmergencyCallInfo(int eccCategory,
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index 9c7993b..1aaa1d3 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -26,6 +26,7 @@
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
 import android.telephony.LinkCapacityEstimate;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseCallState;
@@ -135,13 +136,9 @@
 
         int subId = sender.getSubId();
 
-        if (mFeatureFlags.notifyDataActivityChangedWithSlot()) {
-            int phoneId = sender.getPhoneId();
-            mTelephonyRegistryMgr.notifyDataActivityChanged(phoneId, subId,
-                    sender.getDataActivityState());
-        } else {
-            mTelephonyRegistryMgr.notifyDataActivityChanged(subId, sender.getDataActivityState());
-        }
+        int phoneId = sender.getPhoneId();
+        mTelephonyRegistryMgr.notifyDataActivityChanged(phoneId, subId,
+                sender.getDataActivityState());
     }
 
     @Override
@@ -307,14 +304,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);
     }
@@ -324,6 +335,19 @@
         mTelephonyRegistryMgr.notifyCarrierRoamingNtnModeChanged(sender.getSubId(), active);
     }
 
+    @Override
+    public void notifyCarrierRoamingNtnEligibleStateChanged(Phone sender, boolean eligible) {
+        mTelephonyRegistryMgr.notifyCarrierRoamingNtnEligibleStateChanged(
+                sender.getSubId(), eligible);
+    }
+
+    @Override
+    public void notifyCarrierRoamingNtnAvailableServicesChanged(
+            Phone sender, @NetworkRegistrationInfo.ServiceType int[] availableServices) {
+        mTelephonyRegistryMgr.notifyCarrierRoamingNtnAvailableServicesChanged(
+                sender.getSubId(), availableServices);
+    }
+
     /**
      * 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/DeviceStateMonitor.java b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
index 7bdf2ff..f572bf8 100644
--- a/src/java/com/android/internal/telephony/DeviceStateMonitor.java
+++ b/src/java/com/android/internal/telephony/DeviceStateMonitor.java
@@ -102,6 +102,7 @@
     private final RegistrantList mPhysicalChannelConfigRegistrants = new RegistrantList();
     private final RegistrantList mSignalStrengthReportDecisionCallbackRegistrants =
             new RegistrantList();
+    private final RegistrantList mScreenStateRegistrants = new RegistrantList();
 
     private final NetworkRequest mWifiNetworkRequest =
             new NetworkRequest.Builder()
@@ -217,7 +218,13 @@
                 public void onDisplayAdded(int displayId) { }
 
                 @Override
-                public void onDisplayRemoved(int displayId) { }
+                public void onDisplayRemoved(int displayId) {
+                    /* adapter for virtual display removed */
+                    boolean screenOn = isScreenOn();
+                    Message msg = obtainMessage(EVENT_SCREEN_STATE_CHANGED);
+                    msg.arg1 = screenOn ? 1 : 0;
+                    sendMessage(msg);
+                }
 
                 @Override
                 public void onDisplayChanged(int displayId) {
@@ -509,6 +516,7 @@
     private void onUpdateDeviceState(int eventType, boolean state) {
         final boolean shouldEnableBarringInfoReportsOld = shouldEnableBarringInfoReports();
         final boolean wasHighPowerEnabled = shouldEnableHighPowerConsumptionIndications();
+        boolean wasScreenOn = mIsScreenOn;
         switch (eventType) {
             case EVENT_SCREEN_STATE_CHANGED:
                 if (mIsScreenOn == state) return;
@@ -618,6 +626,13 @@
                 mSignalStrengthReportDecisionCallbackRegistrants.notifyResult(false);
             }
         }
+
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            // Determine whether to notify registrants about the screen on, off state change.
+            if (wasScreenOn != mIsScreenOn) {
+                mScreenStateRegistrants.notifyResult(mIsScreenOn);
+            }
+        }
     }
 
     /**
@@ -809,6 +824,37 @@
     }
 
     /**
+     * Unregister for Screen on, off notifications changed.
+     * @param h Handler to notify
+     */
+    public void unregisterForScreenStateChanged(Handler h) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            Rlog.d(TAG, "unregisterForScreenStateChanged: carrierRoamingNbIotNtn is disabled");
+            return;
+        }
+
+        mScreenStateRegistrants.remove(h);
+    }
+
+    /**
+     * Register a callback to receive the screen on or off.
+     * @param h Handler to notify
+     * @param what msg.what when the message is delivered
+     * @param obj AsyncResult.userObj when the message is delivered
+     */
+    public void registerForScreenStateChanged(Handler h, int what, Object obj) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            Rlog.d(TAG, "registerForScreenStateChanged: carrierRoamingNbIotNtn is disabled");
+            return;
+        }
+        Registrant r = new Registrant(h, what, obj);
+        mScreenStateRegistrants.add(r);
+
+        // Initial notification
+        mScreenStateRegistrants.notifyResult(mIsScreenOn);
+    }
+
+    /**
      * Register a callback to decide whether signal strength should be notified or not.
      * @param h Handler to notify
      */
diff --git a/src/java/com/android/internal/telephony/DisplayInfoController.java b/src/java/com/android/internal/telephony/DisplayInfoController.java
index e8a0566..d3f0264 100644
--- a/src/java/com/android/internal/telephony/DisplayInfoController.java
+++ b/src/java/com/android/internal/telephony/DisplayInfoController.java
@@ -87,23 +87,26 @@
         mLogTag = "DIC-" + mPhone.getPhoneId();
         mServiceState = mPhone.getServiceStateTracker().getServiceState();
         mConfigs = new PersistableBundle();
+        CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
         try {
-            mConfigs = mPhone.getContext().getSystemService(CarrierConfigManager.class)
-                    .getConfigForSubId(mPhone.getSubId(),
-                            CarrierConfigManager.KEY_SHOW_ROAMING_INDICATOR_BOOL);
+            if (ccm != null) {
+                mConfigs = ccm.getConfigForSubId(mPhone.getSubId(),
+                        CarrierConfigManager.KEY_SHOW_ROAMING_INDICATOR_BOOL);
+            }
         } catch (Exception ignored) {
             // CarrierConfigLoader might not be available yet.
             // Once it's available, configs will be updated through the listener.
         }
         mPhone.getServiceStateTracker()
                 .registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
-        mPhone.getContext().getSystemService(CarrierConfigManager.class)
-                .registerCarrierConfigChangeListener(Runnable::run,
-                        (slotIndex, subId, carrierId, specificCarrierId) -> {
-                            if (slotIndex == mPhone.getPhoneId()) {
-                                obtainMessage(EVENT_CARRIER_CONFIG_CHANGED).sendToTarget();
-                            }
-                        });
+        if (ccm != null) {
+            ccm.registerCarrierConfigChangeListener(Runnable::run,
+                    (slotIndex, subId, carrierId, specificCarrierId) -> {
+                        if (slotIndex == mPhone.getPhoneId()) {
+                            obtainMessage(EVENT_CARRIER_CONFIG_CHANGED).sendToTarget();
+                        }
+                    });
+        }
         mTelephonyDisplayInfo = new TelephonyDisplayInfo(
                 TelephonyManager.NETWORK_TYPE_UNKNOWN,
                 TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE,
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/GsmCdmaConnection.java b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
index cc07047..d96663a 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaConnection.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
@@ -30,6 +30,7 @@
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
+import android.telephony.emergency.EmergencyNumber;
 import android.text.TextUtils;
 
 import com.android.internal.telephony.PhoneInternalInterface.DialArgs;
@@ -186,7 +187,7 @@
 
         mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
         if (dialArgs.isEmergency) {
-            setEmergencyCallInfo(mOwner, null);
+            setEmergencyCallInfo(mOwner, dialArgs);
 
             // There was no emergency number info found for this call, however it is
             // still marked as an emergency number. This may happen if it was a redialed
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 93a0c2f..2e1a89f 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -390,8 +390,9 @@
                 .makeDataNetworkController(this, getLooper(), featureFlags);
 
         mCarrierResolver = mTelephonyComponentFactory.inject(CarrierResolver.class.getName())
-                .makeCarrierResolver(this);
-        mCarrierPrivilegesTracker = new CarrierPrivilegesTracker(Looper.myLooper(), this, context);
+                .makeCarrierResolver(this, featureFlags);
+        mCarrierPrivilegesTracker = new CarrierPrivilegesTracker(
+                Looper.myLooper(), this, context, featureFlags);
 
         getCarrierActionAgent().registerForCarrierAction(
                 CarrierActionAgent.CARRIER_ACTION_SET_METERED_APNS_ENABLED, this,
@@ -1119,6 +1120,7 @@
 
     @Override
     public GsmCdmaCall getForegroundCall() {
+        if (!hasCalling()) return null;
         return mCT.mForegroundCall;
     }
 
@@ -1396,6 +1398,8 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public boolean isInCall() {
+        if (!hasCalling()) return false;
+
         GsmCdmaCall.State foregroundCallState = getForegroundCall().getState();
         GsmCdmaCall.State backgroundCallState = getBackgroundCall().getState();
         GsmCdmaCall.State ringingCallState = getRingingCall().getState();
@@ -5494,7 +5498,16 @@
     public void refreshSafetySources(String refreshBroadcastId) {
         if (mFeatureFlags.enableIdentifierDisclosureTransparencyUnsolEvents()
                 || mFeatureFlags.enableModemCipherTransparencyUnsolEvents()) {
-            mSafetySource.refresh(mContext, refreshBroadcastId);
+            post(() -> mSafetySource.refresh(mContext, refreshBroadcastId));
         }
     }
+
+    /**
+     * @return The sms dispatchers controller
+     */
+    @Override
+    @Nullable
+    public SmsDispatchersController getSmsDispatchersController() {
+        return mIccSmsInterfaceManager.mDispatchersController;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
index 7c1670c..3141406 100644
--- a/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/IccSmsInterfaceManager.java
@@ -96,7 +96,6 @@
     final protected Context mContext;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     final protected AppOpsManager mAppOps;
-    @VisibleForTesting
     public SmsDispatchersController mDispatchersController;
     private SmsPermissions mSmsPermissions;
 
@@ -407,15 +406,15 @@
      * A permissions check before passing to {@link IccSmsInterfaceManager#sendDataInternal}.
      * This method checks if the calling package or itself has the permission to send the data sms.
      */
-    public void sendDataWithSelfPermissions(String callingPackage, String callingAttributionTag,
-            String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
-            PendingIntent deliveryIntent, boolean isForVvm) {
+    public void sendDataWithSelfPermissions(String callingPackage, int callingUser,
+            String callingAttributionTag, String destAddr, String scAddr, int destPort, byte[] data,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean isForVvm) {
         if (!mSmsPermissions.checkCallingOrSelfCanSendSms(callingPackage, callingAttributionTag,
                 "Sending SMS message")) {
             returnUnspecifiedFailure(sentIntent);
             return;
         }
-        sendDataInternal(callingPackage, destAddr, scAddr, destPort, data, sentIntent,
+        sendDataInternal(callingPackage, callingUser, destAddr, scAddr, destPort, data, sentIntent,
                 deliveryIntent, isForVvm);
     }
 
@@ -425,9 +424,9 @@
      */
     @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
-            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
-        sendData(callingPackage, null, destAddr, scAddr, destPort, data,
+    public void sendData(String callingPackage, int callingUser, String destAddr, String scAddr,
+            int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
+        sendData(callingPackage, callingUser, null, destAddr, scAddr, destPort, data,
                 sentIntent, deliveryIntent);
     }
 
@@ -435,7 +434,7 @@
      * A permissions check before passing to {@link IccSmsInterfaceManager#sendDataInternal}.
      * This method checks only if the calling package has the permission to send the data sms.
      */
-    public void sendData(String callingPackage, String callingAttributionTag,
+    public void sendData(String callingPackage, int callingUser, String callingAttributionTag,
             String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
             PendingIntent deliveryIntent) {
         if (!mSmsPermissions.checkCallingCanSendSms(callingPackage, callingAttributionTag,
@@ -443,7 +442,7 @@
             returnUnspecifiedFailure(sentIntent);
             return;
         }
-        sendDataInternal(callingPackage, destAddr, scAddr, destPort, data, sentIntent,
+        sendDataInternal(callingPackage, callingUser, destAddr, scAddr, destPort, data, sentIntent,
                 deliveryIntent, false /* isForVvm */);
     }
 
@@ -474,17 +473,17 @@
      *  raw pdu of the status report is in the extended data ("pdu").
      */
 
-    private void sendDataInternal(String callingPackage, String destAddr, String scAddr,
-            int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
-            boolean isForVvm) {
+    private void sendDataInternal(String callingPackage, int callinUser, String destAddr,
+            String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
+            PendingIntent deliveryIntent, boolean isForVvm) {
         if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
             log("sendData: destAddr=" + destAddr + " scAddr=" + scAddr + " destPort="
                     + destPort + " data='" + HexDump.toHexString(data)  + "' sentIntent="
                     + sentIntent + " deliveryIntent=" + deliveryIntent + " isForVVM=" + isForVvm);
         }
         destAddr = filterDestAddress(destAddr);
-        mDispatchersController.sendData(callingPackage, destAddr, scAddr, destPort, data,
-                sentIntent, deliveryIntent, isForVvm);
+        mDispatchersController.sendData(callingPackage, callinUser, destAddr, scAddr,
+                destPort, data, sentIntent, deliveryIntent, isForVvm);
     }
 
     /**
@@ -492,12 +491,13 @@
      * This method checks only if the calling package has the permission to send the sms.
      * Note: SEND_SMS permission should be checked by the caller of this method
      */
-    public void sendText(String callingPackage, String destAddr, String scAddr,
+    public void sendText(String callingPackage, int callingUser, String destAddr, String scAddr,
             String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
             boolean persistMessageForNonDefaultSmsApp, long messageId, boolean skipShortCodeCheck) {
-        sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
-                persistMessageForNonDefaultSmsApp, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
-                false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, false /* isForVvm */,
+        sendTextInternal(callingPackage, callingUser, destAddr, scAddr, text, sentIntent,
+                deliveryIntent, persistMessageForNonDefaultSmsApp,
+                SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
+                SMS_MESSAGE_PERIOD_NOT_SPECIFIED, false /* isForVvm */,
                 messageId, skipShortCodeCheck);
     }
 
@@ -505,27 +505,29 @@
      * A permissions check before passing to {@link IccSmsInterfaceManager#sendTextInternal}.
      * This method checks if the calling package or itself has the permission to send the sms.
      */
-    public void sendTextWithSelfPermissions(String callingPackage, String callingAttributeTag,
-            String destAddr, String scAddr, String text, PendingIntent sentIntent,
-            PendingIntent deliveryIntent, boolean persistMessage, boolean isForVvm) {
+    public void sendTextWithSelfPermissions(String callingPackage, int callingUser,
+            String callingAttributeTag, String destAddr, String scAddr, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage,
+            boolean isForVvm) {
         if (!mSmsPermissions.checkCallingOrSelfCanSendSms(callingPackage, callingAttributeTag,
                 "Sending SMS message")) {
             returnUnspecifiedFailure(sentIntent);
             return;
         }
-        sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
-                persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
-                SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm, 0L /* messageId */);
+        sendTextInternal(callingPackage, callingUser, destAddr, scAddr, text, sentIntent,
+                deliveryIntent, persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
+                false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
+                isForVvm, 0L /* messageId */);
     }
 
 
-    private void sendTextInternal(String callingPackage, String destAddr, String scAddr,
-            String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
+    private void sendTextInternal(String callingPackage, int callingUser, String destAddr,
+            String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
             boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
             int validityPeriod, boolean isForVvm, long messageId) {
-        sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
-                persistMessageForNonDefaultSmsApp, priority, expectMore, validityPeriod, isForVvm,
-                messageId, false);
+        sendTextInternal(callingPackage, callingUser, destAddr, scAddr, text, sentIntent,
+                deliveryIntent, persistMessageForNonDefaultSmsApp, priority, expectMore,
+                validityPeriod, isForVvm, messageId, false);
     }
 
     /**
@@ -577,8 +579,8 @@
      * @param skipShortCodeCheck Skip check for short code type destination address.
      */
 
-    private void sendTextInternal(String callingPackage, String destAddr, String scAddr,
-            String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
+    private void sendTextInternal(String callingPackage, int callingUser, String destAddr,
+            String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
             boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
             int validityPeriod, boolean isForVvm, long messageId, boolean skipShortCodeCheck) {
         if (Rlog.isLoggable("SMS", Log.VERBOSE)) {
@@ -591,7 +593,7 @@
         notifyIfOutgoingEmergencySms(destAddr);
         destAddr = filterDestAddress(destAddr);
         mDispatchersController.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
-                null/*messageUri*/, callingPackage, persistMessageForNonDefaultSmsApp,
+                null/*messageUri*/, callingPackage, callingUser, persistMessageForNonDefaultSmsApp,
                 priority, expectMore, validityPeriod, isForVvm, messageId, skipShortCodeCheck);
     }
 
@@ -641,18 +643,19 @@
      *  Any Other values including negative considered as Invalid Validity Period of the message.
      */
 
-    public void sendTextWithOptions(String callingPackage, String callingAttributionTag,
-            String destAddr, String scAddr, String text, PendingIntent sentIntent,
-            PendingIntent deliveryIntent, boolean persistMessageForNonDefaultSmsApp, int priority,
+    public void sendTextWithOptions(String callingPackage, int callingUser,
+            String callingAttributionTag, String destAddr, String scAddr, String text,
+            PendingIntent sentIntent, PendingIntent deliveryIntent,
+            boolean persistMessageForNonDefaultSmsApp, int priority,
             boolean expectMore, int validityPeriod) {
         if (!mSmsPermissions.checkCallingCanSendText(persistMessageForNonDefaultSmsApp,
                     callingPackage, callingAttributionTag, "Sending SMS message")) {
             returnUnspecifiedFailure(sentIntent);
             return;
         }
-        sendTextInternal(callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
-                persistMessageForNonDefaultSmsApp, priority, expectMore, validityPeriod,
-                false /* isForVvm */, 0L /* messageId */);
+        sendTextInternal(callingPackage, callingUser, destAddr, scAddr, text, sentIntent,
+                deliveryIntent, persistMessageForNonDefaultSmsApp, priority, expectMore,
+                validityPeriod, false /* isForVvm */, 0L /* messageId */);
     }
 
     /**
@@ -718,12 +721,12 @@
      *                 Used for logging and diagnostics purposes. The id may be 0.
      */
 
-    public void sendMultipartText(String callingPackage, String callingAttributionTag,
-            String destAddr, String scAddr, List<String> parts, List<PendingIntent> sentIntents,
-            List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp,
-            long messageId) {
-        sendMultipartTextWithOptions(callingPackage, callingAttributionTag, destAddr, scAddr, parts,
-                sentIntents, deliveryIntents, persistMessageForNonDefaultSmsApp,
+    public void sendMultipartText(String callingPackage, int callingUser,
+            String callingAttributionTag, String destAddr, String scAddr, List<String> parts,
+            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+            boolean persistMessageForNonDefaultSmsApp, long messageId) {
+        sendMultipartTextWithOptions(callingPackage, callingUser, callingAttributionTag, destAddr,
+                scAddr, parts, sentIntents, deliveryIntents, persistMessageForNonDefaultSmsApp,
                 SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, false /* expectMore */,
                 SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
                 messageId);
@@ -778,10 +781,11 @@
      *                 Used for logging and diagnostics purposes. The id may be 0.
      */
 
-    public void sendMultipartTextWithOptions(String callingPackage, String callingAttributionTag,
-            String destAddr, String scAddr, List<String> parts, List<PendingIntent> sentIntents,
-            List<PendingIntent> deliveryIntents, boolean persistMessageForNonDefaultSmsApp,
-            int priority, boolean expectMore, int validityPeriod, long messageId) {
+    public void sendMultipartTextWithOptions(String callingPackage, int callingUser,
+            String callingAttributionTag, String destAddr, String scAddr, List<String> parts,
+            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents,
+            boolean persistMessageForNonDefaultSmsApp, int priority, boolean expectMore,
+            int validityPeriod, long messageId) {
         if (!mSmsPermissions.checkCallingCanSendText(persistMessageForNonDefaultSmsApp,
                 callingPackage, callingAttributionTag, "Sending SMS message")) {
             returnUnspecifiedFailure(sentIntents);
@@ -821,7 +825,7 @@
                 }
 
                 mDispatchersController.sendText(destAddr, scAddr, singlePart, singleSentIntent,
-                        singleDeliveryIntent, null /* messageUri */, callingPackage,
+                        singleDeliveryIntent, null /* messageUri */, callingPackage, callingUser,
                         persistMessageForNonDefaultSmsApp, priority, expectMore, validityPeriod,
                         false /* isForVvm */, messageId);
             }
@@ -829,12 +833,12 @@
         }
 
         mDispatchersController.sendMultipartText(destAddr,
-                                      scAddr,
-                                      (ArrayList<String>) parts,
-                                      (ArrayList<PendingIntent>) sentIntents,
-                                      (ArrayList<PendingIntent>) deliveryIntents,
-                                      null, callingPackage, persistMessageForNonDefaultSmsApp,
-                                          priority, expectMore, validityPeriod, messageId);
+                scAddr,
+                (ArrayList<String>) parts,
+                (ArrayList<PendingIntent>) sentIntents,
+                (ArrayList<PendingIntent>) deliveryIntents,
+                null, callingPackage, callingUser, persistMessageForNonDefaultSmsApp,
+                priority, expectMore, validityPeriod, messageId);
 
     }
 
@@ -1292,12 +1296,13 @@
      */
     @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void sendStoredText(String callingPkg, Uri messageUri, String scAddress,
+    public void sendStoredText(String callingPkg, int callingUser, Uri messageUri, String scAddress,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
-        sendStoredText(callingPkg, null, messageUri, scAddress, sentIntent, deliveryIntent);
+        sendStoredText(callingPkg, callingUser, null, messageUri,
+                scAddress, sentIntent, deliveryIntent);
     }
 
-    public void sendStoredText(String callingPkg, String callingAttributionTag,
+    public void sendStoredText(String callingPkg, int callingUser, String callingAttributionTag,
             Uri messageUri, String scAddress, PendingIntent sentIntent,
             PendingIntent deliveryIntent) {
         if (!mSmsPermissions.checkCallingCanSendSms(callingPkg, callingAttributionTag,
@@ -1324,7 +1329,7 @@
         notifyIfOutgoingEmergencySms(textAndAddress[1]);
         textAndAddress[1] = filterDestAddress(textAndAddress[1]);
         mDispatchersController.sendText(textAndAddress[1], scAddress, textAndAddress[0],
-                sentIntent, deliveryIntent, messageUri, callingPkg,
+                sentIntent, deliveryIntent, messageUri, callingPkg, callingUser,
                 true /* persistMessageForNonDefaultSmsApp */, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
                 false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, false /* isForVvm */,
                 0L /* messageId */);
@@ -1336,13 +1341,14 @@
      */
     @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public void sendStoredMultipartText(String callingPkg, Uri messageUri, String scAddress,
-            List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
-        sendStoredMultipartText(callingPkg, null, messageUri, scAddress, sentIntents,
-                deliveryIntents);
+    public void sendStoredMultipartText(String callingPkg, int callingUser,
+            Uri messageUri, String scAddress, List<PendingIntent> sentIntents,
+            List<PendingIntent> deliveryIntents) {
+        sendStoredMultipartText(callingPkg, callingUser, null,
+                messageUri, scAddress, sentIntents, deliveryIntents);
     }
 
-    public void sendStoredMultipartText(String callingPkg,
+    public void sendStoredMultipartText(String callingPkg, int callingUser,
             String callingAttributionTag, Uri messageUri, String scAddress,
             List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
         if (!mSmsPermissions.checkCallingCanSendSms(callingPkg, callingAttributionTag,
@@ -1395,7 +1401,7 @@
 
                 mDispatchersController.sendText(textAndAddress[1], scAddress, singlePart,
                         singleSentIntent, singleDeliveryIntent, messageUri, callingPkg,
-                        true  /* persistMessageForNonDefaultSmsApp */,
+                        callingUser, true  /* persistMessageForNonDefaultSmsApp */,
                         SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
                         false /* expectMore */, SMS_MESSAGE_PERIOD_NOT_SPECIFIED,
                         false /* isForVvm */, 0L /* messageId */);
@@ -1410,7 +1416,7 @@
                 (ArrayList<PendingIntent>) sentIntents,
                 (ArrayList<PendingIntent>) deliveryIntents,
                 messageUri,
-                callingPkg,
+                callingPkg, callingUser,
                 true  /* persistMessageForNonDefaultSmsApp */,
                 SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
                 false /* expectMore */,
diff --git a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
index 1a6bf2b..6955a55 100644
--- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
+++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
@@ -202,9 +202,8 @@
                         tracker.onSent(mContext);
                         mTrackers.remove(token);
                         mPhone.notifySmsSent(tracker.mDestAddress);
-                        mSmsDispatchersController.notifySmsSentToEmergencyStateTracker(
-                                tracker.mDestAddress, tracker.mMessageId, true,
-                                tracker.isSinglePartOrLastPart());
+                        mSmsDispatchersController.notifySmsSent(tracker, true,
+                                tracker.isSinglePartOrLastPart(), true /*success*/);
                         break;
                     case ImsSmsImplBase.SEND_STATUS_ERROR:
                         tracker.onFailed(mContext, reason, networkReasonCode);
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index 49d1adc..011e67b 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -30,6 +30,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
@@ -65,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;
 
@@ -73,7 +75,10 @@
 import com.android.internal.telephony.SmsConstants.MessageClass;
 import com.android.internal.telephony.analytics.TelephonyAnalytics;
 import com.android.internal.telephony.analytics.TelephonyAnalytics.SmsMmsAnalytics;
+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;
@@ -284,6 +289,8 @@
 
     private List<SmsFilter> mSmsFilters;
 
+    protected final @NonNull FeatureFlags mFeatureFlags;
+
     /**
      * Create a new SMS broadcast helper.
      * @param name the class name for logging
@@ -291,18 +298,19 @@
      * @param storageMonitor the SmsStorageMonitor to check for storage availability
      */
     protected InboundSmsHandler(String name, Context context, SmsStorageMonitor storageMonitor,
-            Phone phone, Looper looper) {
+            Phone phone, Looper looper, FeatureFlags featureFlags) {
         super(name, looper);
 
+        mFeatureFlags = featureFlags;
         mContext = context;
         mStorageMonitor = storageMonitor;
         mPhone = phone;
         mResolver = context.getContentResolver();
-        mWapPush = new WapPushOverSms(context);
+        mWapPush = new WapPushOverSms(context, mFeatureFlags);
 
-        boolean smsCapable = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_sms_capable);
-        mSmsReceiveDisabled = !TelephonyManager.from(mContext).getSmsReceiveCapableForPhone(
+        TelephonyManager telephonyManager = TelephonyManager.from(mContext);
+        boolean smsCapable = telephonyManager.isDeviceSmsCapable();
+        mSmsReceiveDisabled = !telephonyManager.getSmsReceiveCapableForPhone(
                 mPhone.getPhoneId(), smsCapable);
 
         PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -681,6 +689,17 @@
             result = RESULT_SMS_DISPATCH_FAILURE;
         }
 
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            if (result == Intents.RESULT_SMS_HANDLED) {
+                SatelliteController satelliteController = SatelliteController.getInstance();
+                if (satelliteController == null) {
+                    log("SatelliteController is not initialized");
+                    return;
+                }
+                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) {
@@ -741,6 +760,11 @@
             return Intents.RESULT_SMS_HANDLED;
         }
 
+        if (isMtSmsPollingMessage(smsb)) {
+            log("Received MT SMS polling message. Ignored.");
+            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
@@ -801,7 +825,12 @@
             Intent intent = new Intent(Intents.SMS_REJECTED_ACTION);
             intent.putExtra("result", result);
             intent.putExtra("subId", mPhone.getSubId());
-            mContext.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
+            if (mFeatureFlags.hsumBroadcast()) {
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                        android.Manifest.permission.RECEIVE_SMS);
+            } else {
+                mContext.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
+            }
         }
         acknowledgeLastIncomingSms(success, result, response);
     }
@@ -1064,7 +1093,7 @@
 
         SmsBroadcastReceiver resultReceiver = tracker.getSmsBroadcastReceiver(this);
 
-        if (!mUserManager.isUserUnlocked()) {
+        if (!isMainUserUnlocked()) {
             log("processMessagePart: !isUserUnlocked; calling processMessagePartWithUserLocked. "
                     + "Port: " + destPort, tracker.getMessageId());
             return processMessagePartWithUserLocked(
@@ -1182,6 +1211,15 @@
         return false;
     }
 
+    private boolean isMainUserUnlocked() {
+        UserHandle mainUser = mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser() ?
+                mUserManager.getMainUser() : null;
+        if (mainUser != null) {
+            return mUserManager.isUserUnlocked(mainUser);
+        }
+        return mUserManager.isUserUnlocked();
+    }
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void showNewMessageNotification() {
         // Do not show the notification on non-FBE devices.
@@ -1205,8 +1243,15 @@
                 .setChannelId(NotificationChannelController.CHANNEL_ID_SMS);
         NotificationManager mNotificationManager =
             (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-        mNotificationManager.notify(
-                NOTIFICATION_TAG, NOTIFICATION_ID_NEW_MESSAGE, mBuilder.build());
+        UserHandle mainUser = mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser() ?
+                mUserManager.getMainUser() : null;
+        if (mainUser != null) {
+            mNotificationManager.notifyAsUser(
+                    NOTIFICATION_TAG, NOTIFICATION_ID_NEW_MESSAGE, mBuilder.build(), mainUser);
+        } else {
+            mNotificationManager.notify(
+                    NOTIFICATION_TAG, NOTIFICATION_ID_NEW_MESSAGE, mBuilder.build());
+        }
     }
 
     static void cancelNewMessageNotification(Context context) {
@@ -1324,6 +1369,7 @@
      * @param user user to deliver the intent to
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @SuppressLint("MissingPermission")
     public void dispatchIntent(Intent intent, String permission, String appOp,
             Bundle opts, SmsBroadcastReceiver resultReceiver, UserHandle user, int subId) {
         intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
@@ -1352,12 +1398,17 @@
             // Get a list of currently started users.
             int[] users = null;
             final List<UserHandle> userHandles = mUserManager.getUserHandles(false);
+            final UserHandle mainUser = mUserManager.getMainUser();
             final List<UserHandle> runningUserHandles = new ArrayList();
             for (UserHandle handle : userHandles) {
                 if (mUserManager.isUserRunning(handle)) {
                     runningUserHandles.add(handle);
                 } else {
-                    if (handle.equals(UserHandle.SYSTEM)) {
+                    if (mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser()
+                            && handle.equals(mainUser)) {
+                        logeWithLocalLog("dispatchIntent: MAIN user is not running",
+                                resultReceiver.mInboundSmsTracker.getMessageId());
+                    } else if (handle.equals(UserHandle.SYSTEM)) {
                         logeWithLocalLog("dispatchIntent: SYSTEM user is not running",
                                 resultReceiver.mInboundSmsTracker.getMessageId());
                     }
@@ -1375,7 +1426,7 @@
             // by user policy.
             for (int i = users.length - 1; i >= 0; i--) {
                 UserHandle targetUser = UserHandle.of(users[i]);
-                if (users[i] != UserHandle.SYSTEM.getIdentifier()) {
+                if (!isMainUser(users[i])) {
                     // Is the user not allowed to use SMS?
                     if (hasUserRestriction(UserManager.DISALLOW_SMS, targetUser)) {
                         continue;
@@ -1385,14 +1436,14 @@
                         continue;
                     }
                 }
-                // Only pass in the resultReceiver when the user SYSTEM is processed.
+                // Only pass in the resultReceiver when the MAIN user is processed.
                 try {
-                    if (users[i] == UserHandle.SYSTEM.getIdentifier()) {
+                    if (isMainUser(users[i])) {
                         resultReceiver.setWaitingForIntent(intent);
                     }
                     mContext.createPackageContextAsUser(mContext.getPackageName(), 0, targetUser)
                             .sendOrderedBroadcast(intent, Activity.RESULT_OK, permission, appOp,
-                                    users[i] == UserHandle.SYSTEM.getIdentifier()
+                                    isMainUser(users[i])
                                             ? resultReceiver : null, getHandler(),
                                     null /* initialData */, null /* initialExtras */, opts);
                 } catch (PackageManager.NameNotFoundException ignored) {
@@ -1416,6 +1467,15 @@
         return (sources != null && !sources.isEmpty());
     }
 
+    @SuppressLint("MissingPermission")
+    private  boolean isMainUser(int userId) {
+        if (mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser()) {
+            return userId == mUserManager.getMainUser().getIdentifier();
+        } else {
+            return userId == UserHandle.SYSTEM.getIdentifier();
+        }
+    }
+
     /**
      * Helper for {@link SmsBroadcastUndelivered} to delete an old message in the raw table.
      */
@@ -1511,7 +1571,11 @@
         }
 
         if (userHandle == null) {
-            userHandle = UserHandle.SYSTEM;
+            if (mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser()) {
+                userHandle = mUserManager.getMainUser();
+            } else {
+                userHandle = UserHandle.SYSTEM;
+            }
         }
         Bundle options = handleSmsWhitelisting(intent.getComponent(), isClass0);
         dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
@@ -1739,6 +1803,7 @@
             handleAction(intent, true);
         }
 
+        @SuppressLint("MissingPermission")
         private synchronized void handleAction(@NonNull Intent intent, boolean onReceive) {
             String action = intent.getAction();
             if (mWaitingForIntent == null || !mWaitingForIntent.getAction().equals(action)) {
@@ -1795,9 +1860,15 @@
                 String mimeType = intent.getType();
 
                 setWaitingForIntent(intent);
-                dispatchIntent(intent, WapPushOverSms.getPermissionForType(mimeType),
-                        WapPushOverSms.getAppOpsStringPermissionForIntent(mimeType), options, this,
-                        UserHandle.SYSTEM, subId);
+                if (mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser()) {
+                    dispatchIntent(intent, WapPushOverSms.getPermissionForType(mimeType),
+                            WapPushOverSms.getAppOpsStringPermissionForIntent(mimeType), options,
+                            this, mUserManager.getMainUser(), subId);
+                } else {
+                    dispatchIntent(intent, WapPushOverSms.getPermissionForType(mimeType),
+                            WapPushOverSms.getAppOpsStringPermissionForIntent(mimeType), options,
+                            this, UserHandle.SYSTEM, subId);
+                }
             } else {
                 // Now that the intents have been deleted we can clean up the PDU data.
                 if (!Intents.DATA_SMS_RECEIVED_ACTION.equals(action)
@@ -1917,6 +1988,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.
      */
@@ -2098,11 +2180,16 @@
         public void onReceive(Context context, Intent intent) {
             if (ACTION_OPEN_SMS_APP.equals(intent.getAction())) {
                 // do nothing if the user had not unlocked the device yet
+                // TODO(b/355049884): This is looking at sms package of the wrong user!
                 UserManager userManager =
                         (UserManager) context.getSystemService(Context.USER_SERVICE);
+                PackageManager pm = context.getPackageManager();
+                if (Flags.hsumPackageManager()) {
+                    pm = context.createContextAsUser(UserHandle.CURRENT, 0).getPackageManager();
+                }
                 if (userManager.isUserUnlocked()) {
-                    context.startActivity(context.getPackageManager().getLaunchIntentForPackage(
-                            Telephony.Sms.getDefaultSmsPackage(context)));
+                    context.startActivityAsUser(pm.getLaunchIntentForPackage(
+                            Telephony.Sms.getDefaultSmsPackage(context)), UserHandle.CURRENT);
                 }
             }
         }
diff --git a/src/java/com/android/internal/telephony/LocaleTracker.java b/src/java/com/android/internal/telephony/LocaleTracker.java
index 0afe119..42ec8d3 100644
--- a/src/java/com/android/internal/telephony/LocaleTracker.java
+++ b/src/java/com/android/internal/telephony/LocaleTracker.java
@@ -566,7 +566,7 @@
             }
 
             if (mFeatureFlags.oemEnabledSatelliteFlag()) {
-                TelephonyCountryDetector.getInstance(mPhone.getContext())
+                TelephonyCountryDetector.getInstance(mPhone.getContext(), mFeatureFlags)
                         .onNetworkCountryCodeChanged(mPhone, countryIso);
             }
             Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED);
diff --git a/src/java/com/android/internal/telephony/MockModem.java b/src/java/com/android/internal/telephony/MockModem.java
index a20e748..1fcdc2a 100644
--- a/src/java/com/android/internal/telephony/MockModem.java
+++ b/src/java/com/android/internal/telephony/MockModem.java
@@ -24,11 +24,13 @@
 import static android.telephony.TelephonyManager.HAL_SERVICE_SIM;
 import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE;
 
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.IBinder;
+import android.os.UserHandle;
 
 import com.android.telephony.Rlog;
 
@@ -158,7 +160,8 @@
         intent.setAction(actionName + phoneId);
         intent.putExtra(PHONE_ID, phoneId);
 
-        status = mContext.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+        status = mContext.bindServiceAsUser(intent, serviceConnection, Context.BIND_AUTO_CREATE,
+                UserHandle.of(ActivityManager.getCurrentUser()));
         return status;
     }
 
diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index a14ae89..881adde 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -43,6 +43,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelUuid;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.telephony.CarrierConfigManager;
@@ -247,9 +248,11 @@
 
         CarrierConfigManager ccm = mContext.getSystemService(CarrierConfigManager.class);
         // Listener callback is executed on handler thread to directly handle config change
-        ccm.registerCarrierConfigChangeListener(this::post,
-                (slotIndex, subId, carrierId, specificCarrierId) ->
-                        onCarrierConfigChanged(slotIndex, subId));
+        if (ccm != null) {
+            ccm.registerCarrierConfigChangeListener(this::post,
+                    (slotIndex, subId, carrierId, specificCarrierId) ->
+                            onCarrierConfigChanged(slotIndex, subId));
+        }
 
         mConvertedPsimSubId = getConvertedPsimSubscriptionId();
     }
@@ -510,8 +513,8 @@
         SatelliteController satelliteController = SatelliteController.getInstance();
         boolean isSatelliteEnabledOrBeingEnabled = false;
         if (satelliteController != null) {
-            isSatelliteEnabledOrBeingEnabled = satelliteController.isSatelliteEnabled()
-                    || satelliteController.isSatelliteBeingEnabled();
+            isSatelliteEnabledOrBeingEnabled =
+                    satelliteController.isSatelliteEnabledOrBeingEnabled();
         }
 
         if (DBG) {
@@ -640,7 +643,7 @@
         if (DBG) log("updateDefaultValues: change: " + change);
         if (change == PRIMARY_SUB_NO_CHANGE) return;
 
-        // If there's only one primary subscription active, we trigger PREFERRED_PICK_DIALOG
+        // If there's only one primary subscription active, we trigger mobile data
         // dialog if and only if there were multiple primary SIM cards and one is removed.
         // Otherwise, if user just inserted their first SIM, or there's one primary and one
         // opportunistic subscription active (activeSubInfos.size() > 1), we automatically
@@ -656,7 +659,19 @@
             if (hasCalling()) mSubscriptionManagerService.setDefaultVoiceSubId(subId);
             if (hasMessaging()) mSubscriptionManagerService.setDefaultSmsSubId(subId);
             if (!mSubscriptionManagerService.isEsimBootStrapProvisioningActivated()) {
-                sendDefaultSubConfirmedNotification(subId);
+                // Determines the appropriate notification type
+                // Preconditions:
+                // - There is only one active primary subscription.
+                // - The eSIM bootstrap is NOT activated.
+                // Behavior:
+                // - If the primary subscription is not deactivated OR the device is in single SIM
+                //   mode, send a notification to dismiss the SIM dialog.
+                // - Otherwise, send a notification to trigger the preferred SIM/data pick dialog.
+                @TelephonyManager.DefaultSubscriptionSelectType
+                int type = (change != PRIMARY_SUB_REMOVED || mActiveModemCount == 1)
+                        ? EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DISMISS
+                        : EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL;
+                sendDefaultSubConfirmedNotification(type, subId);
             }
             return;
         }
@@ -768,17 +783,21 @@
         }
     }
 
-    private void sendDefaultSubConfirmedNotification(int defaultSubId) {
+    private void sendDefaultSubConfirmedNotification(
+            @TelephonyManager.DefaultSubscriptionSelectType int type, int defaultSubId) {
         Intent intent = new Intent();
         intent.setAction(ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED);
         intent.setClassName("com.android.settings",
                 "com.android.settings.sim.SimSelectNotification");
 
-        intent.putExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE,
-                EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DISMISS);
+        intent.putExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE, type);
         intent.putExtra(EXTRA_SUBSCRIPTION_ID, defaultSubId);
 
-        mContext.sendBroadcast(intent);
+        if (mFeatureFlags.hsumBroadcast()) {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        } else {
+            mContext.sendBroadcast(intent);
+        }
     }
 
     private void sendSubChangeNotificationIfNeeded(int change, boolean dataSelected,
@@ -816,7 +835,11 @@
             if (simCombinationParams.mWarningType == EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA) {
                 intent.putExtra(EXTRA_SIM_COMBINATION_NAMES, simCombinationParams.mSimNames);
             }
-            mContext.sendBroadcast(intent);
+            if (mFeatureFlags.hsumBroadcast()) {
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            } else {
+                mContext.sendBroadcast(intent);
+            }
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
index 91cd4ec..cc2e94d 100644
--- a/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
+++ b/src/java/com/android/internal/telephony/NetworkRegistrationManager.java
@@ -75,17 +75,19 @@
 
         CarrierConfigManager ccm = phone.getContext().getSystemService(CarrierConfigManager.class);
         // Callback directly calls rebindService and should be executed in handler thread
-        ccm.registerCarrierConfigChangeListener(
-                this::post,
-                (slotIndex, subId, carrierId, specificCarrierId) -> {
-                    if (slotIndex == phone.getPhoneId()) {
-                        // We should wait for carrier config changed event because the target
-                        // binding package name can come from the carrier config. Note that
-                        // we still get this event even when SIM is absent.
-                        logd("Carrier config changed. Try to bind network service.");
-                        rebindService();
-                    }
-                });
+        if (ccm != null) {
+            ccm.registerCarrierConfigChangeListener(
+                    this::post,
+                    (slotIndex, subId, carrierId, specificCarrierId) -> {
+                        if (slotIndex == phone.getPhoneId()) {
+                            // We should wait for carrier config changed event because the target
+                            // binding package name can come from the carrier config. Note that
+                            // we still get this event even when SIM is absent.
+                            logd("Carrier config changed. Try to bind network service.");
+                            rebindService();
+                        }
+                    });
+        }
 
         PhoneConfigurationManager.registerForMultiSimConfigChange(
                 this, EVENT_BIND_NETWORK_SERVICE, null);
diff --git a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
index c09f8fb..9b2b847 100644
--- a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
+++ b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
@@ -33,6 +33,7 @@
 import android.os.Messenger;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.telephony.CellInfo;
 import android.telephony.LocationAccessPolicy;
 import android.telephony.NetworkScan;
@@ -567,8 +568,8 @@
         //   3. The live scan is not requested by mobile network setting menu
         private synchronized boolean interruptLiveScan(NetworkScanRequestInfo nsri) {
             if (mLiveRequestInfo != null && mPendingRequestInfo == null
-                    && nsri.mUid == Process.SYSTEM_UID
-                            && mLiveRequestInfo.mUid != Process.SYSTEM_UID) {
+                    && UserHandle.isSameApp(nsri.mUid, Process.SYSTEM_UID)
+                            && !UserHandle.isSameApp(mLiveRequestInfo.mUid, Process.SYSTEM_UID)) {
                 doInterruptScan(mLiveRequestInfo.mScanId);
                 mPendingRequestInfo = nsri;
                 notifyMessenger(mLiveRequestInfo, TelephonyScanManager.CALLBACK_SCAN_ERROR,
diff --git a/src/java/com/android/internal/telephony/NetworkTypeController.java b/src/java/com/android/internal/telephony/NetworkTypeController.java
index 67ca1e1..081a5c8 100644
--- a/src/java/com/android/internal/telephony/NetworkTypeController.java
+++ b/src/java/com/android/internal/telephony/NetworkTypeController.java
@@ -170,7 +170,6 @@
                 @Override
                 public void onQosSessionsChanged(
                         @NonNull List<QosBearerSession> qosBearerSessions) {
-                    if (!mIsTimerResetEnabledOnVoiceQos) return;
                     sendMessage(obtainMessage(EVENT_QOS_SESSION_CHANGED, qosBearerSessions));
                 }
 
@@ -233,6 +232,8 @@
     private int mLastAnchorNrCellId = PhysicalChannelConfig.PHYSICAL_CELL_ID_UNKNOWN;
     private boolean mDoesPccListIndicateIdle = false;
 
+    private boolean mInVoiceCall = false;
+
     /**
      * NetworkTypeController constructor.
      *
@@ -315,7 +316,9 @@
         filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
         mPhone.getContext().registerReceiver(mIntentReceiver, filter, null, mPhone);
         CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
-        ccm.registerCarrierConfigChangeListener(Runnable::run, mCarrierConfigChangeListener);
+        if (ccm != null) {
+            ccm.registerCarrierConfigChangeListener(Runnable::run, mCarrierConfigChangeListener);
+        }
     }
 
     private void unRegisterForAllEvents() {
@@ -327,7 +330,7 @@
                 mDataNetworkControllerCallback);
         mPhone.getContext().unregisterReceiver(mIntentReceiver);
         CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
-        if (mCarrierConfigChangeListener != null) {
+        if (ccm != null && mCarrierConfigChangeListener != null) {
             ccm.unregisterCarrierConfigChangeListener(mCarrierConfigChangeListener);
         }
     }
@@ -712,21 +715,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);
@@ -1022,7 +1026,10 @@
                     if (rat == TelephonyManager.NETWORK_TYPE_NR
                             || (isLte(rat) && isNrConnected())) {
                         if (isNrAdvanced()) {
-                            transitionTo(mNrConnectedAdvancedState);
+                            // Move into idle state because mPhysicalLinkStatus indicated idle,
+                            // ignored any advance reason because unless mPhysicalLinkStatus changed
+                            // again, shouldn't move back to advance.
+                            log("Ignore NR advanced from cached PCC/RatchetedNrBands while idle");
                         } else if (isPhysicalLinkActive()) {
                             transitionWithTimerTo(mNrConnectedState);
                         } else {
@@ -1309,6 +1316,7 @@
             mRatchetedNrBands.addAll(nrBands);
         } else {
             if (mFeatureFlags.supportNrSaRrcIdle() && mDoesPccListIndicateIdle
+                    && anchorNrCellId != mLastAnchorNrCellId
                     && isUsingPhysicalChannelConfigForRrcDetection()
                     && !mPrimaryCellChangedWhileIdle
                     && !isNrAdvancedForPccFields(nrBandwidths, nrBands)) {
@@ -1347,11 +1355,13 @@
         if (secondaryRule != null) {
             int secondaryDuration = secondaryRule.getSecondaryTimer(mSecondaryTimerState);
             long durationMillis = secondaryDuration * 1000L;
-            if ((mSecondaryTimerExpireTimestamp - SystemClock.uptimeMillis()) > durationMillis) {
+            long now = SystemClock.uptimeMillis();
+            if ((mSecondaryTimerExpireTimestamp - now) > durationMillis) {
                 if (DBG) log("Due to PCI change, reduce the secondary timer to " + durationMillis);
                 removeMessages(EVENT_SECONDARY_TIMER_EXPIRED);
                 sendMessageDelayed(EVENT_SECONDARY_TIMER_EXPIRED, mSecondaryTimerState,
                         durationMillis);
+                mSecondaryTimerExpireTimestamp = now + durationMillis;
             }
         } else {
             loge("!! Secondary timer is active, but found no rule for " + mPrimaryTimerState);
@@ -1363,6 +1373,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);
@@ -1387,7 +1399,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;
@@ -1746,6 +1761,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 aa62acb..ca82d31 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -126,6 +126,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;
@@ -281,9 +282,6 @@
     // Key used to read/write the ID for storing the call forwarding status
     public static final String CF_ID = "cf_id_key";
 
-    // Key used to read/write "disable DNS server check" pref (used for testing)
-    private static final String DNS_SERVER_CHECK_DISABLED_KEY = "dns_server_check_disabled_key";
-
     // Integer used to let the calling application know that the we are ignoring auto mode switch.
     private static final int ALREADY_IN_AUTO_SELECTION = 1;
 
@@ -334,7 +332,6 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public CommandsInterface mCi;
     protected int mVmCount = 0;
-    private boolean mDnsCheckDisabled;
     protected DataNetworkController mDataNetworkController;
     /* Used for dispatching signals to configured carrier apps */
     protected CarrierSignalAgent mCarrierSignalAgent;
@@ -486,6 +483,7 @@
     private static final String ALLOWED_NETWORK_TYPES_TEXT_POWER = "power";
     private static final String ALLOWED_NETWORK_TYPES_TEXT_CARRIER = "carrier";
     private static final String ALLOWED_NETWORK_TYPES_TEXT_ENABLE_2G = "enable_2g";
+    private static final String ALLOWED_NETWORK_TYPES_TEXT_TEST = "test";
     private static final int INVALID_ALLOWED_NETWORK_TYPES = -1;
     protected boolean mIsCarrierNrSupported = false;
     protected boolean mIsAllowedNetworkTypesLoadedFromDb = false;
@@ -615,7 +613,6 @@
         setUnitTestMode(unitTestMode);
 
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
-        mDnsCheckDisabled = sp.getBoolean(DNS_SERVER_CHECK_DISABLED_KEY, false);
         mCi.setOnCallRing(this, EVENT_CALL_RING, null);
 
         /* "Voice capable" means that this device supports circuit-switched
@@ -661,7 +658,7 @@
         // Initialize device storage and outgoing SMS usage monitors for SMSDispatchers.
         mTelephonyComponentFactory = telephonyComponentFactory;
         mSmsStorageMonitor = mTelephonyComponentFactory.inject(SmsStorageMonitor.class.getName())
-                .makeSmsStorageMonitor(this);
+                .makeSmsStorageMonitor(this, mFeatureFlags);
         mSmsUsageMonitor = mTelephonyComponentFactory.inject(SmsUsageMonitor.class.getName())
                 .makeSmsUsageMonitor(context);
         mUiccController = UiccController.getInstance();
@@ -902,6 +899,8 @@
                         Rlog.e(mLogTag, "Invalid Exception for usage setting " + ar.exception);
                         break; // technically extraneous, but good hygiene
                     }
+                } else {
+                    mUsageSettingFromModem = msg.arg1;
                 }
                 break;
             default:
@@ -984,26 +983,6 @@
     protected abstract void onUpdateIccAvailability();
 
     /**
-     * Disables the DNS check (i.e., allows "0.0.0.0").
-     * Useful for lab testing environment.
-     * @param b true disables the check, false enables.
-     */
-    public void disableDnsCheck(boolean b) {
-        mDnsCheckDisabled = b;
-        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
-        SharedPreferences.Editor editor = sp.edit();
-        editor.putBoolean(DNS_SERVER_CHECK_DISABLED_KEY, b);
-        editor.apply();
-    }
-
-    /**
-     * Returns true if the DNS check is currently disabled.
-     */
-    public boolean isDnsCheckDisabled() {
-        return mDnsCheckDisabled;
-    }
-
-    /**
      * Register for getting notifications for change in the Call State {@link Call.State}
      * This is called PreciseCallState because the call state is more precise than the
      * {@link PhoneConstants.State} which can be obtained using the {@link PhoneStateListener}
@@ -2512,6 +2491,8 @@
                 return TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER;
             case ALLOWED_NETWORK_TYPES_TEXT_ENABLE_2G:
                 return TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G;
+            case ALLOWED_NETWORK_TYPES_TEXT_TEST:
+                return TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_TEST;
             default:
                 return INVALID_ALLOWED_NETWORK_TYPES;
         }
@@ -2535,6 +2516,8 @@
                 return ALLOWED_NETWORK_TYPES_TEXT_CARRIER;
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
                 return ALLOWED_NETWORK_TYPES_TEXT_ENABLE_2G;
+            case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_TEST:
+                return ALLOWED_NETWORK_TYPES_TEXT_TEST;
             default:
                 throw new IllegalArgumentException(
                         "No DB name conversion available for allowed network type reason: " + reason
@@ -3199,7 +3182,11 @@
             Intent intent = new Intent(TelephonyIntents.SECRET_CODE_ACTION,
                     Uri.parse("android_secret_code://" + code));
             intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-            mContext.sendBroadcast(intent, null, options.toBundle());
+            if (mFeatureFlags.hsumBroadcast()) {
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL, null, options.toBundle());
+            } else {
+                mContext.sendBroadcast(intent, null, options.toBundle());
+            }
 
             // {@link TelephonyManager.ACTION_SECRET_CODE} will replace {@link
             // TelephonyIntents#SECRET_CODE_ACTION} in the next Android version. Before
@@ -3207,7 +3194,12 @@
             Intent secrectCodeIntent = new Intent(TelephonyManager.ACTION_SECRET_CODE,
                     Uri.parse("android_secret_code://" + code));
             secrectCodeIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-            mContext.sendBroadcast(secrectCodeIntent, null, options.toBundle());
+            if (mFeatureFlags.hsumBroadcast()) {
+                mContext.sendBroadcastAsUser(secrectCodeIntent, UserHandle.ALL, null,
+                        options.toBundle());
+            } else {
+                mContext.sendBroadcast(secrectCodeIntent, null, options.toBundle());
+            }
         }
     }
 
@@ -4495,7 +4487,8 @@
             mCi.getUsageSetting(obtainMessage(EVENT_GET_USAGE_SETTING_DONE));
             // If the modem value is already known, and the value has changed, proceed to update.
         } else if (mPreferredUsageSetting != mUsageSettingFromModem) {
-            mCi.setUsageSetting(obtainMessage(EVENT_SET_USAGE_SETTING_DONE),
+            mCi.setUsageSetting(obtainMessage(EVENT_SET_USAGE_SETTING_DONE,
+                        mPreferredUsageSetting, 0 /* unused */),
                     mPreferredUsageSetting);
         }
         return true;
@@ -4921,6 +4914,14 @@
     }
 
     /**
+     * @return The sms dispatchers controller
+     */
+    @Nullable
+    public SmsDispatchersController getSmsDispatchersController() {
+        return null;
+    }
+
+    /**
      * @return The data settings manager
      */
     @NonNull
@@ -5295,22 +5296,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);
     }
 
@@ -5326,11 +5348,42 @@
         mNotifier.notifyCarrierRoamingNtnModeChanged(this, active);
     }
 
+    /**
+     * Notify external listeners that the device eligibility to connect to carrier roaming
+     * non-terrestrial network changed.
+     *
+     * @param eligible {@code true} when the device is eligible for satellite
+     * communication if all the following conditions are met:
+     * <ul>
+     * <li>Any subscription on the device supports P2P satellite messaging which is defined by
+     * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li>
+     * <li>{@link CarrierConfigManager#KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT} set to
+     * {@link CarrierConfigManager#CARRIER_ROAMING_NTN_CONNECT_MANUAL} </li>
+     * <li>The device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi,
+     * and the hysteresis timer defined by {@link CarrierConfigManager
+     * #KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT} is expired. </li>
+     * </ul>
+     */
+    public void notifyCarrierRoamingNtnEligibleStateChanged(boolean eligible) {
+        logd("notifyCarrierRoamingNtnEligibleStateChanged eligible:" + eligible);
+        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);
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Phone: subId=" + getSubId());
         pw.println(" mPhoneId=" + mPhoneId);
         pw.println(" mCi=" + mCi);
-        pw.println(" mDnsCheckDisabled=" + mDnsCheckDisabled);
         pw.println(" mDoesRilSendMultipleCallRing=" + mDoesRilSendMultipleCallRing);
         pw.println(" mCallRingContinueToken=" + mCallRingContinueToken);
         pw.println(" mCallRingDelay=" + mCallRingDelay);
@@ -5345,7 +5398,6 @@
         pw.println(" mNotifier=" + mNotifier);
         pw.println(" mSimulatedRadioControl=" + mSimulatedRadioControl);
         pw.println(" mUnitTestMode=" + mUnitTestMode);
-        pw.println(" isDnsCheckDisabled()=" + isDnsCheckDisabled());
         pw.println(" getUnitTestMode()=" + getUnitTestMode());
         pw.println(" getState()=" + getState());
         pw.println(" getIccSerialNumber()=" + Rlog.pii(mLogTag, getIccSerialNumber()));
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
index ffa5b69..dd96594 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -29,6 +29,7 @@
 import android.os.PowerManager;
 import android.os.RegistrantList;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.sysprop.TelephonyProperties;
 import android.telephony.PhoneCapability;
@@ -391,9 +392,12 @@
                             }
                             mSlotsSupportingSimultaneousCellularCalls.add(i);
                         }
-                        // Ensure the slots supporting cellular DSDA does not exceed the phone count
-                        if (mSlotsSupportingSimultaneousCellularCalls.size() > getPhoneCount()) {
-                            loge("Invalid size of DSDA slots. Disabling cellular DSDA.");
+                        // Ensure the number of slots supporting cellular DSDA is valid:
+                        if (mSlotsSupportingSimultaneousCellularCalls.size() > getPhoneCount() ||
+                                mSlotsSupportingSimultaneousCellularCalls.size() < 2) {
+                            loge("Invalid size of DSDA slots. Disabling cellular DSDA. Size of "
+                                    + "mSlotsSupportingSimultaneousCellularCalls=" +
+                                    mSlotsSupportingSimultaneousCellularCalls.size());
                             mSlotsSupportingSimultaneousCellularCalls.clear();
                         }
                     } else {
@@ -770,7 +774,11 @@
 
         Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED);
         intent.putExtra(EXTRA_ACTIVE_SIM_SUPPORTED_COUNT, numOfActiveModems);
-        mContext.sendBroadcast(intent);
+        if (mFeatureFlags.hsumBroadcast()) {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        } else {
+            mContext.sendBroadcast(intent);
+        }
     }
     /**
      * This is invoked from shell commands during CTS testing only.
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index d9c5c9c..c077ca9 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -46,6 +46,7 @@
 import com.android.internal.telephony.data.CellularNetworkValidator;
 import com.android.internal.telephony.data.PhoneSwitcher;
 import com.android.internal.telephony.data.TelephonyNetworkFactory;
+import com.android.internal.telephony.data.TelephonyNetworkProvider;
 import com.android.internal.telephony.euicc.EuiccCardController;
 import com.android.internal.telephony.euicc.EuiccController;
 import com.android.internal.telephony.flags.FeatureFlags;
@@ -101,6 +102,7 @@
     static private SimultaneousCallingTracker sSimultaneousCallingTracker;
     static private PhoneSwitcher sPhoneSwitcher;
     static private TelephonyNetworkFactory[] sTelephonyNetworkFactories;
+    private static TelephonyNetworkProvider sTelephonyNetworkProvider;
     static private NotificationChannelController sNotificationChannelController;
     static private CellularNetworkValidator sCellularNetworkValidator;
 
@@ -205,7 +207,7 @@
 
                 // Instantiate UiccController so that all other classes can just
                 // call getInstance()
-                sUiccController = UiccController.make(context);
+                sUiccController = UiccController.make(context, featureFlags);
 
                 Rlog.i(LOG_TAG, "Creating SubscriptionManagerService");
                 sSubscriptionManagerService = new SubscriptionManagerService(context,
@@ -239,6 +241,12 @@
                 }
                 Rlog.i(LOG_TAG, "defaultSmsApplication: " + packageName);
 
+                if (sFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser()) {
+                    // Explicitly call this, even if the user has no default Sms application, to
+                    // ensure that the System apps have the appropriate permissions.
+                    SmsApplication.grantPermissionsToSystemApps(context);
+                }
+
                 // Set up monitor to watch for changes to SMS packages
                 SmsApplication.initSmsPackageMonitor(context);
 
@@ -279,9 +287,15 @@
 
                 sNotificationChannelController = new NotificationChannelController(context);
 
-                for (int i = 0; i < numPhones; i++) {
-                    sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
-                            Looper.myLooper(), sPhones[i], featureFlags);
+                if (featureFlags.supportNetworkProvider()) {
+                    // Create the TelephonyNetworkProvider instance, which is a singleton.
+                    sTelephonyNetworkProvider = new TelephonyNetworkProvider(Looper.myLooper(),
+                            context, featureFlags);
+                } else {
+                    for (int i = 0; i < numPhones; i++) {
+                        sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
+                                Looper.myLooper(), sPhones[i], featureFlags);
+                    }
                 }
             }
         }
@@ -306,7 +320,10 @@
 
             sPhones = copyOf(sPhones, activeModemCount);
             sCommandsInterfaces = copyOf(sCommandsInterfaces, activeModemCount);
-            sTelephonyNetworkFactories = copyOf(sTelephonyNetworkFactories, activeModemCount);
+
+            if (!sFeatureFlags.supportNetworkProvider()) {
+                sTelephonyNetworkFactories = copyOf(sTelephonyNetworkFactories, activeModemCount);
+            }
 
             int cdmaSubscription = CdmaSubscriptionSourceManager.getDefault(context);
             for (int i = prevActiveModemCount; i < activeModemCount; i++) {
@@ -318,8 +335,11 @@
                         PackageManager.FEATURE_TELEPHONY_IMS)) {
                     sPhones[i].createImsPhone();
                 }
-                sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
-                        Looper.myLooper(), sPhones[i], sFeatureFlags);
+
+                if (!sFeatureFlags.supportNetworkProvider()) {
+                    sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
+                            Looper.myLooper(), sPhones[i], sFeatureFlags);
+                }
             }
         }
     }
@@ -387,6 +407,10 @@
         }
     }
 
+    public static TelephonyNetworkProvider getNetworkProvider() {
+        return sTelephonyNetworkProvider;
+    }
+
     /**
      * Get the network factory associated with a given phone ID.
      * @param phoneId the phone id
@@ -573,13 +597,22 @@
             pw.flush();
             pw.println("++++++++++++++++++++++++++++++++");
 
-            sTelephonyNetworkFactories[i].dump(fd, pw, args);
+            if (!sFeatureFlags.supportNetworkProvider()) {
+                sTelephonyNetworkFactories[i].dump(fd, pw, args);
+            }
 
             pw.flush();
             pw.decreaseIndent();
             pw.println("++++++++++++++++++++++++++++++++");
         }
 
+        pw.increaseIndent();
+        if (sFeatureFlags.supportNetworkProvider()) {
+            sTelephonyNetworkProvider.dump(fd, pw, args);
+        }
+        pw.decreaseIndent();
+        pw.println("++++++++++++++++++++++++++++++++");
+
         pw.println("UiccController:");
         pw.increaseIndent();
         try {
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
index 9f459f5..f652370 100644
--- a/src/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.java
@@ -26,6 +26,7 @@
 import android.telephony.CellIdentity;
 import android.telephony.CellInfo;
 import android.telephony.LinkCapacityEstimate;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneCapability;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseDataConnectionState;
@@ -145,7 +146,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,
@@ -156,4 +162,11 @@
 
     /** Notify carrier roaming non-terrestrial network mode changed. **/
     void notifyCarrierRoamingNtnModeChanged(Phone sender, boolean active);
+
+    /** 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);
 }
diff --git a/src/java/com/android/internal/telephony/PhoneSubInfoController.java b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
index 073e242..7ee3de2 100644
--- a/src/java/com/android/internal/telephony/PhoneSubInfoController.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
@@ -785,9 +785,16 @@
      */
     @Nullable
     private String getCurrentPackageName() {
+        if (mFeatureFlags.hsumPackageManager()) {
+            PackageManager pm = mContext.createContextAsUser(Binder.getCallingUserHandle(), 0)
+                    .getPackageManager();
+            if (pm == null) return null;
+            String[] callingPackageNames = pm.getPackagesForUid(Binder.getCallingUid());
+            return (callingPackageNames == null) ? null : callingPackageNames[0];
+        }
         if (mPackageManager == null) return null;
-        String[] callingUids = mPackageManager.getPackagesForUid(Binder.getCallingUid());
-        return (callingUids == null) ? null : callingUids[0];
+        String[] callingPackageNames = mPackageManager.getPackagesForUid(Binder.getCallingUid());
+        return (callingPackageNames == null) ? null : callingPackageNames[0];
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/ProxyController.java b/src/java/com/android/internal/telephony/ProxyController.java
index 01aed07..3b30d2f 100644
--- a/src/java/com/android/internal/telephony/ProxyController.java
+++ b/src/java/com/android/internal/telephony/ProxyController.java
@@ -28,6 +28,7 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
+import android.os.UserHandle;
 import android.telephony.RadioAccessFamily;
 import android.telephony.TelephonyManager;
 import android.util.Log;
@@ -364,7 +365,11 @@
                     logd("onStartRadioCapabilityResponse got exception=" + ar.exception);
                     mRadioCapabilitySessionId = mUniqueIdGenerator.getAndIncrement();
                     Intent intent = new Intent(TelephonyIntents.ACTION_SET_RADIO_CAPABILITY_FAILED);
-                    mContext.sendBroadcast(intent);
+                    if (mFlags.hsumBroadcast()) {
+                        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+                    } else {
+                        mContext.sendBroadcast(intent);
+                    }
                     clearTransaction();
                     return;
                 }
@@ -611,7 +616,12 @@
         }
 
         // Broadcast that we're done
-        mContext.sendBroadcast(intent, android.Manifest.permission.READ_PHONE_STATE);
+        if (mFlags.hsumBroadcast()) {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                    android.Manifest.permission.READ_PHONE_STATE);
+        } else {
+            mContext.sendBroadcast(intent, android.Manifest.permission.READ_PHONE_STATE);
+        }
     }
 
     // Clear this transaction
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index beeaaa7..34ac832 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -1050,10 +1050,16 @@
                     }
                 } else {
                     mDisabledRadioServices.get(service).add(mPhoneId);
-                    mHalVersion.put(service, RADIO_HAL_VERSION_UNKNOWN);
-                    riljLoge("getRadioServiceProxy: set " + serviceToString(service) + " for "
-                            + HIDL_SERVICE_NAME[mPhoneId] + " as disabled\n"
-                            + android.util.Log.getStackTraceString(new RuntimeException()));
+                    if (isRadioServiceSupported(service)) {
+                        mHalVersion.put(service, RADIO_HAL_VERSION_UNKNOWN);
+                        riljLoge("getRadioServiceProxy: set " + serviceToString(service) + " for "
+                                + HIDL_SERVICE_NAME[mPhoneId] + " as disabled\n"
+                                + android.util.Log.getStackTraceString(new RuntimeException()));
+                    } else {
+                        mHalVersion.put(service, RADIO_HAL_VERSION_UNSUPPORTED);
+                        riljLog("getRadioServiceProxy: set " + serviceToString(service) + " for "
+                                + HIDL_SERVICE_NAME[mPhoneId] + " as disabled (unsupported)");
+                    }
                 }
             }
         } catch (RemoteException e) {
@@ -4241,7 +4247,7 @@
             riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                     + " with data profiles : ");
             for (DataProfile profile : dps) {
-                riljLog(profile.toString());
+                riljLog(Objects.toString(profile, "DataProfile is null"));
             }
         }
 
@@ -4377,9 +4383,9 @@
             riljLog(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest)
                     + " params: " + carrierRestrictionRules);
         }
-
         radioServiceInvokeHelper(HAL_SERVICE_SIM, rr, "setAllowedCarriers", () -> {
-            simProxy.setAllowedCarriers(rr.mSerial, carrierRestrictionRules);
+            simProxy.setAllowedCarriers(rr.mSerial, carrierRestrictionRules,
+                    getHalVersion(HAL_SERVICE_SIM));
         });
     }
 
diff --git a/src/java/com/android/internal/telephony/RILUtils.java b/src/java/com/android/internal/telephony/RILUtils.java
index 8897db4..a81dbc8 100644
--- a/src/java/com/android/internal/telephony/RILUtils.java
+++ b/src/java/com/android/internal/telephony/RILUtils.java
@@ -1944,6 +1944,31 @@
     }
 
     /**
+     * Convert a list of CarrierIdentifiers into an array of CarrierInfo.aidl
+     *
+     * @param carriers List of CarrierIdentifiers
+     * @return The converted array of CarrierInfos.
+     */
+    public static android.hardware.radio.sim.CarrierInfo[] convertToHalCarrierInfoListAidl(
+            List<CarrierIdentifier> carriers) {
+        android.hardware.radio.sim.CarrierInfo[] result =
+                new android.hardware.radio.sim.CarrierInfo[carriers.size()];
+        for (int i = 0; i < carriers.size(); i++) {
+            CarrierIdentifier ci = carriers.get(i);
+            android.hardware.radio.sim.CarrierInfo carrierInfo =
+                    new android.hardware.radio.sim.CarrierInfo();
+            carrierInfo.mcc = convertNullToEmptyString(ci.getMcc());
+            carrierInfo.mnc = convertNullToEmptyString(ci.getMnc());
+            carrierInfo.spn = ci.getSpn();
+            carrierInfo.imsiPrefix = ci.getImsi();
+            carrierInfo.gid1 = ci.getGid1();
+            carrierInfo.gid2 = ci.getGid2();
+            result[i] = carrierInfo;
+        }
+        return result;
+    }
+
+    /**
      * Convert to Dial defined in radio/1.0/types.hal
      * @param address Address
      * @param clirMode CLIR mode
diff --git a/src/java/com/android/internal/telephony/RadioConfig.java b/src/java/com/android/internal/telephony/RadioConfig.java
index da20639..b4db14c 100644
--- a/src/java/com/android/internal/telephony/RadioConfig.java
+++ b/src/java/com/android/internal/telephony/RadioConfig.java
@@ -466,7 +466,7 @@
         RILRequest rr = obtainRequest(RIL_REQUEST_SET_PREFERRED_DATA_MODEM,
                 result, mDefaultWorkSource);
         if (DBG) {
-            logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest));
+            logd(rr.serialString() + "> " + RILUtils.requestToString(rr.mRequest) + " " + modemId);
         }
         try {
             proxy.setPreferredDataModem(rr.mSerial, modemId);
diff --git a/src/java/com/android/internal/telephony/RadioSimProxy.java b/src/java/com/android/internal/telephony/RadioSimProxy.java
index 5265692..1c864fe 100644
--- a/src/java/com/android/internal/telephony/RadioSimProxy.java
+++ b/src/java/com/android/internal/telephony/RadioSimProxy.java
@@ -24,6 +24,8 @@
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
 import com.android.internal.telephony.uicc.SimPhonebookRecord;
 
+import java.util.Collections;
+
 /**
  * A holder for IRadioSim.
  * Use getAidl to get IRadioSim and call the AIDL implementations of the HAL APIs.
@@ -512,27 +514,44 @@
 
     /**
      * Call IRadioSim#setAllowedCarriers
-     * @param serial Serial number of request
+     *
+     * @param serial                  Serial number of request
      * @param carrierRestrictionRules Allowed carriers
-     * @throws RemoteException
      */
-    public void setAllowedCarriers(int serial, CarrierRestrictionRules carrierRestrictionRules)
-            throws RemoteException {
+    public void setAllowedCarriers(int serial, CarrierRestrictionRules carrierRestrictionRules,
+            HalVersion halversion) throws RemoteException {
         if (isEmpty()) return;
         if (isAidl()) {
-            // Prepare structure with allowed list, excluded list and priority
             android.hardware.radio.sim.CarrierRestrictions carrierRestrictions =
                     new android.hardware.radio.sim.CarrierRestrictions();
-            carrierRestrictions.allowedCarriers = RILUtils.convertToHalCarrierRestrictionListAidl(
-                    carrierRestrictionRules.getAllowedCarriers());
-            carrierRestrictions.excludedCarriers = RILUtils.convertToHalCarrierRestrictionListAidl(
-                    carrierRestrictionRules.getExcludedCarriers());
+            if (halversion.greaterOrEqual(RIL.RADIO_HAL_VERSION_2_2)) {
+                carrierRestrictions.allowedCarrierInfoList =
+                        RILUtils.convertToHalCarrierInfoListAidl(
+                                carrierRestrictionRules.getAllowedCarriers());
+                carrierRestrictions.excludedCarrierInfoList =
+                        RILUtils.convertToHalCarrierInfoListAidl(
+                                carrierRestrictionRules.getExcludedCarriers());
+                carrierRestrictions.allowedCarriers =
+                        RILUtils.convertToHalCarrierRestrictionListAidl(Collections.EMPTY_LIST);
+                carrierRestrictions.excludedCarriers =
+                        RILUtils.convertToHalCarrierRestrictionListAidl(Collections.EMPTY_LIST);
+            } else {
+                // Prepare structure with allowed list, excluded list and priority
+                carrierRestrictions.allowedCarriers =
+                        RILUtils.convertToHalCarrierRestrictionListAidl(
+                                carrierRestrictionRules.getAllowedCarriers());
+                carrierRestrictions.excludedCarriers =
+                        RILUtils.convertToHalCarrierRestrictionListAidl(
+                                carrierRestrictionRules.getExcludedCarriers());
+            }
             carrierRestrictions.allowedCarriersPrioritized =
                     (carrierRestrictionRules.getDefaultCarrierRestriction()
                             == CarrierRestrictionRules.CARRIER_RESTRICTION_DEFAULT_NOT_ALLOWED);
+            carrierRestrictions.status = carrierRestrictionRules.getCarrierRestrictionStatus();
             mSimProxy.setAllowedCarriers(serial, carrierRestrictions,
                     RILUtils.convertToHalSimLockMultiSimPolicyAidl(
                             carrierRestrictionRules.getMultiSimPolicy()));
+            Rlog.d(TAG, "RadioSimProxy setAllowedCarriers params = " + carrierRestrictions);
         } else {
             // Prepare structure with allowed list, excluded list and priority
             android.hardware.radio.V1_4.CarrierRestrictionsWithPriority carrierRestrictions =
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 8764e02..8a2d5bf 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;
@@ -52,7 +53,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
-import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -89,6 +89,7 @@
 import com.android.internal.telephony.analytics.TelephonyAnalytics;
 import com.android.internal.telephony.analytics.TelephonyAnalytics.SmsMmsAnalytics;
 import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IccRecords;
@@ -255,8 +256,7 @@
         mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
                 Settings.Global.SMS_SHORT_CODE_RULE), false, mSettingsObserver);
 
-        mSmsCapable = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_sms_capable);
+        mSmsCapable = mTelephonyManager.isDeviceSmsCapable();
         mSmsSendDisabled = !mTelephonyManager.getSmsSendCapableForPhone(
                 mPhone.getPhoneId(), mSmsCapable);
         IntentFilter intentFilter = new IntentFilter();
@@ -1017,8 +1017,8 @@
      */
     protected void notifySmsSentFailedToEmergencyStateTracker(SmsTracker tracker,
             boolean isOverIms) {
-        mSmsDispatchersController.notifySmsSentFailedToEmergencyStateTracker(
-                tracker.mDestAddress, tracker.mMessageId, isOverIms);
+        mSmsDispatchersController.notifySmsSent(tracker, isOverIms,
+            true /*isLastSmsPart*/, false /*success*/);
     }
 
     /**
@@ -1053,9 +1053,8 @@
             }
             tracker.onSent(mContext);
             mPhone.notifySmsSent(tracker.mDestAddress);
-            mSmsDispatchersController.notifySmsSentToEmergencyStateTracker(
-                    tracker.mDestAddress, tracker.mMessageId, false,
-                    tracker.isSinglePartOrLastPart());
+            mSmsDispatchersController.notifySmsSent(tracker, false,
+                tracker.isSinglePartOrLastPart(), true /*success*/);
 
             mPhone.getSmsStats().onOutgoingSms(
                     tracker.mImsRetry > 0 /* isOverIms */,
@@ -1398,17 +1397,19 @@
      *  raw pdu of the status report is in the extended data ("pdu").
      */
     @UnsupportedAppUsage
-    protected void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
-            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent, boolean isForVvm) {
+    protected void sendData(String callingPackage, int callingUser, String destAddr, String scAddr,
+            int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
+            boolean isForVvm, long uniqueMessageId) {
         int messageRef = nextMessageRef();
         SmsMessageBase.SubmitPduBase pdu = getSubmitPdu(
                 scAddr, destAddr, destPort, data, (deliveryIntent != null), messageRef);
         if (pdu != null) {
             HashMap map = getSmsTrackerMap(destAddr, scAddr, destPort, data, pdu);
-            SmsTracker tracker = getSmsTracker(callingPackage, map, sentIntent, deliveryIntent,
-                    getFormat(), null /*messageUri*/, false /*expectMore*/,
+            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);
@@ -1521,11 +1522,12 @@
      */
     public void sendText(String destAddr, String scAddr, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri,
-            String callingPkg, boolean persistMessage, int priority,
+            String callingPkg, int callingUser, boolean persistMessage, int priority,
             boolean expectMore, int validityPeriod, boolean isForVvm,
             long messageId) {
         sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, messageUri, callingPkg,
-                persistMessage, priority, expectMore, validityPeriod, isForVvm, messageId, false);
+                callingUser, persistMessage, priority, expectMore, validityPeriod, isForVvm,
+                messageId, false, PendingRequest.getNextUniqueMessageId());
     }
 
     /**
@@ -1630,10 +1632,10 @@
      * @param skipShortCodeCheck Skip check for short code type destination address.
      */
     public void sendText(String destAddr, String scAddr, String text,
-                         PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri,
-                         String callingPkg, boolean persistMessage, int priority,
-                         boolean expectMore, int validityPeriod, boolean isForVvm,
-                         long messageId, boolean skipShortCodeCheck) {
+            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 uniqueMessageId) {
         Rlog.d(TAG, "sendText id: " + SmsController.formatCrossStackMessageId(messageId));
         int messageRef = nextMessageRef();
         SmsMessageBase.SubmitPduBase pdu = getSubmitPdu(
@@ -1641,10 +1643,10 @@
                 messageRef);
         if (pdu != null) {
             HashMap map = getSmsTrackerMap(destAddr, scAddr, text, pdu);
-            SmsTracker tracker = getSmsTracker(callingPkg, map, sentIntent, deliveryIntent,
-                    getFormat(), messageUri, expectMore, text, true /*isText*/,
+            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);
@@ -1825,8 +1827,8 @@
     public void sendMultipartText(String destAddr, String scAddr,
             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
             ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
-            boolean persistMessage, int priority, boolean expectMore, int validityPeriod,
-            long messageId) {
+            int callingUser, boolean persistMessage, int priority, boolean expectMore,
+            int validityPeriod, long messageId, long uniqueMessageId) {
         final String fullMessageText = getMultipartMessageText(parts);
         int refNumber = getNextConcatenatedRef() & 0x00FF;
         int encoding = SmsConstants.ENCODING_UNKNOWN;
@@ -1885,11 +1887,11 @@
             }
             int messageRef = nextMessageRef();
             trackers[i] =
-                getNewSubmitPduTracker(callingPkg, destAddr, scAddr, parts.get(i), smsHeader,
-                        encoding, sentIntent, deliveryIntent, (i == (msgCount - 1)),
+                getNewSubmitPduTracker(callingPkg, callingUser, destAddr, scAddr, parts.get(i),
+                        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;
@@ -1920,12 +1922,12 @@
     /**
      * Create a new SubmitPdu and return the SMS tracker.
      */
-    private SmsTracker getNewSubmitPduTracker(String callingPackage, String destinationAddress,
-            String scAddress, String message, SmsHeader smsHeader, int encoding,
-            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart,
+    private SmsTracker getNewSubmitPduTracker(String callingPackage, int callingUser,
+            String destinationAddress, String scAddress, String message, SmsHeader smsHeader,
+            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;
@@ -1951,11 +1953,11 @@
             if (submitPdu != null) {
                 HashMap map = getSmsTrackerMap(destinationAddress, scAddress,
                         message, submitPdu);
-                return getSmsTracker(callingPackage, map, sentIntent, deliveryIntent,
-                        getFormat(), unsentPartCount, anyPartFailed, messageUri, smsHeader,
-                        (!lastPart || expectMore), fullMessageText, true /*isText*/,
-                        true /*persistMessage*/, priority, validityPeriod, false /* isForVvm */,
-                        messageId, messageRef, false);
+                return getSmsTracker(callingPackage, callingUser, map, sentIntent,
+                        deliveryIntent, getFormat(), unsentPartCount, anyPartFailed, messageUri,
+                        smsHeader, (!lastPart || expectMore), fullMessageText,  /*isText*/
+                        true,  /*persistMessage*/ true, priority, validityPeriod,  /* isForVvm */
+                        false, messageId, messageRef, false, uniqueMessageId);
             } else {
                 Rlog.e(TAG, "CdmaSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned "
                         + "null " + SmsController.formatCrossStackMessageId(messageId));
@@ -1969,11 +1971,12 @@
                             smsHeader.languageShiftTable, validityPeriod, messageRef);
             if (pdu != null) {
                 HashMap map = getSmsTrackerMap(destinationAddress, scAddress, message, pdu);
-                return getSmsTracker(callingPackage, map, sentIntent,
-                        deliveryIntent, getFormat(), unsentPartCount, anyPartFailed, messageUri,
-                        smsHeader, (!lastPart || expectMore), fullMessageText, true /*isText*/,
-                        false /*persistMessage*/, priority, validityPeriod, false /* isForVvm */,
-                        messageId, messageRef, false);
+                return getSmsTracker(callingPackage, callingUser, map,
+                        sentIntent, deliveryIntent, getFormat(), unsentPartCount, anyPartFailed,
+                        messageUri, smsHeader, (!lastPart || expectMore),
+                        fullMessageText,  /*isText*/
+                        true,  /*persistMessage*/ false, priority, validityPeriod,  /* isForVvm */
+                        false, messageId, messageRef, false, uniqueMessageId);
             } else {
                 Rlog.e(TAG, "GsmSMSDispatcher.getNewSubmitPduTracker(): getSubmitPdu() returned "
                         + "null " + SmsController.formatCrossStackMessageId(messageId));
@@ -2490,6 +2493,7 @@
 
         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");
@@ -2500,7 +2504,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;
@@ -2527,6 +2532,26 @@
             mMessageId = messageId;
             mCarrierId = carrierId;
             mSkipShortCodeDestAddrCheck = skipShortCodeDestAddrCheck;
+            mUniqueMessageId = uniqueMessageId;
+        }
+
+        @VisibleForTesting
+        public SmsTracker(String destAddr, long messageId) {
+            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;
         }
 
         public HashMap<String, Object> getData() {
@@ -2812,16 +2837,21 @@
         }
     }
 
-    protected SmsTracker getSmsTracker(String callingPackage, HashMap<String, Object> data,
+    protected SmsTracker getSmsTracker(String callingPackage, int callingUser,
+            HashMap<String, Object> data,
             PendingIntent sentIntent, PendingIntent deliveryIntent, String format,
             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();
+        }
+
         // Get package info via packagemanager
-        UserHandle callingUser = UserHandle.getUserHandleForUid(Binder.getCallingUid());
-        final int userId = callingUser.getIdentifier();
-        PackageManager pm = mContext.createContextAsUser(callingUser, 0).getPackageManager();
+        PackageManager pm = mContext.createContextAsUser(UserHandle.of(callingUser), 0)
+                .getPackageManager();
         PackageInfo appInfo = null;
         try {
             appInfo = pm.getPackageInfo(callingPackage, PackageManager.GET_SIGNATURES);
@@ -2833,31 +2863,35 @@
         String destAddr = PhoneNumberUtils.extractNetworkPortion((String) data.get("destAddr"));
         return new SmsTracker(data, sentIntent, deliveryIntent, appInfo, destAddr, format,
                 unsentPartCount, anyPartFailed, messageUri, smsHeader, expectMore,
-                fullMessageText, getSubId(), isText, persistMessage, userId, priority,
+                fullMessageText, getSubId(), isText, persistMessage, callingUser, priority,
                 validityPeriod, isForVvm, messageId, mPhone.getCarrierId(), messageRef,
-                skipShortCodeCheck);
+                skipShortCodeCheck, uniqueMessageId);
     }
 
-    protected SmsTracker getSmsTracker(String callingPackage, 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) {
-        return getSmsTracker(callingPackage, data, sentIntent, deliveryIntent, format,
-                null/*unsentPartCount*/, null/*anyPartFailed*/, messageUri, null/*smsHeader*/,
-                expectMore, fullMessageText, isText, persistMessage,
-                SMS_MESSAGE_PRIORITY_NOT_SPECIFIED, SMS_MESSAGE_PERIOD_NOT_SPECIFIED, isForVvm,
-                messageId, messageRef, false);
+    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 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, uniqueMessageId);
     }
 
-    protected SmsTracker getSmsTracker(String callingPackage, 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) {
-        return getSmsTracker(callingPackage, data, sentIntent, deliveryIntent, format,
-                null/*unsentPartCount*/, null/*anyPartFailed*/, messageUri, null/*smsHeader*/,
-                expectMore, fullMessageText, isText, persistMessage, priority, validityPeriod,
-                isForVvm, messageId, messageRef, skipShortCodeCheck);
+    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,
+            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,
+                uniqueMessageId);
     }
 
     protected HashMap<String, Object> getSmsTrackerMap(String destAddr, String scAddr,
@@ -3029,24 +3063,6 @@
         return SubscriptionManager.getSubscriptionId(mPhone.getPhoneId());
     }
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private void checkCallerIsPhoneOrCarrierApp() {
-        int uid = Binder.getCallingUid();
-        int appId = UserHandle.getAppId(uid);
-        if (appId == Process.PHONE_UID || uid == 0) {
-            return;
-        }
-        try {
-            PackageManager pm = mContext.getPackageManager();
-            ApplicationInfo ai = pm.getApplicationInfo(getCarrierAppPackageName(), 0);
-            if (UserHandle.getAppId(ai.uid) != UserHandle.getAppId(Binder.getCallingUid())) {
-                throw new SecurityException("Caller is not phone or carrier app!");
-            }
-        } catch (PackageManager.NameNotFoundException re) {
-            throw new SecurityException("Caller is not phone or carrier app!");
-        }
-    }
-
     protected boolean isCdmaMo() {
         return mSmsDispatchersController.isCdmaMo();
     }
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 2a9bf7f..3309d21 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -327,18 +327,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 +444,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 +551,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();
                 }
             }
         }
@@ -666,7 +658,9 @@
         mCarrierConfig = getCarrierConfig();
         CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
         // Callback which directly handle config change should be executed in handler thread
-        ccm.registerCarrierConfigChangeListener(this::post, mCarrierConfigChangeListener);
+        if (ccm != null) {
+            ccm.registerCarrierConfigChangeListener(this::post, mCarrierConfigChangeListener);
+        }
 
         mAccessNetworksManager = mPhone.getAccessNetworksManager();
         mOutOfServiceSS = new ServiceState();
@@ -1162,6 +1156,9 @@
 
         mDesiredPowerState = power;
         setPowerStateToDesired(forEmergencyCall, isSelectedPhoneForEmergencyCall, forceApply);
+        if (mDesiredPowerState) {
+            SatelliteController.getInstance().onSetCellularRadioPowerStateRequested(true);
+        }
     }
 
     /**
@@ -1240,7 +1237,7 @@
                 if (mUiccApplication == null
                         || mUiccApplication.getState() != AppState.APPSTATE_READY) {
                     mIsSimReady = false;
-                    updateSpnDisplay();
+                    updateCarrierDisplayName();
                 }
                 break;
 
@@ -1323,6 +1320,12 @@
                     // Hence, issuing shut down regardless of radio power response
                     mCi.requestShutdown(null);
                 }
+
+                ar = (AsyncResult) msg.obj;
+                if (ar.exception != null) {
+                    loge("EVENT_RADIO_POWER_OFF_DONE: exception=" + ar.exception);
+                    SatelliteController.getInstance().onPowerOffCellularRadioFailed();
+                }
                 break;
 
             // GSM
@@ -1413,7 +1416,7 @@
                 updateOtaspState();
                 if (mPhone.isPhoneTypeGsm()) {
                     mCdnr.updateEfFromUsim((SIMRecords) mIccRecords);
-                    updateSpnDisplay();
+                    updateCarrierDisplayName();
                 }
                 break;
 
@@ -1520,7 +1523,7 @@
 
             case EVENT_IMS_CAPABILITY_CHANGED:
                 if (DBG) log("EVENT_IMS_CAPABILITY_CHANGED");
-                updateSpnDisplay();
+                updateCarrierDisplayName();
                 mImsCapabilityChangedRegistrants.notifyRegistrants();
                 break;
 
@@ -1612,7 +1615,7 @@
                     mCdnr.updateEfFromRuim((RuimRecords) mIccRecords);
                     updatePhoneObject();
                     if (mPhone.isPhoneTypeCdma()) {
-                        updateSpnDisplay();
+                        updateCarrierDisplayName();
                     } else {
                         RuimRecords ruim = (RuimRecords) mIccRecords;
                         if (ruim != null) {
@@ -2750,59 +2753,42 @@
         }
     }
 
-    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;
             if (showSpn) {
-                // Need to show both plmn and spn if both are not same.
-                if (!Objects.equals(spn, plmn)) {
+                if (TextUtils.isEmpty(carrierName)) {
+                    carrierName = spn;
+                } else if (!TextUtils.isEmpty(spn) && !Objects.equals(spn, carrierName)) {
+                    // Need to show both plmn and spn if both are not same.
                     String separator = mPhone.getContext().getString(
                             com.android.internal.R.string.kg_text_message_separator).toString();
                     carrierName = new StringBuilder().append(carrierName).append(separator)
@@ -2812,29 +2798,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;
@@ -2875,11 +2866,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;
             }
@@ -2895,10 +2887,8 @@
         }
 
         String crossSimSpnFormat = null;
-        if (mPhone.getImsPhone() != null
-                && (mPhone.getImsPhone() != null)
-                && (mPhone.getImsPhone().getImsRegistrationTech()
-                == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM)) {
+        if ((getImsRegistrationTech() == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM)
+                && mPhone.isImsRegistered()) {
             // In Cross SIM Calling mode show SPN or PLMN + Cross SIM Calling
             //
             // 1) Show SPN + Cross SIM Calling If SIM has SPN and SPN display condition
@@ -2915,7 +2905,7 @@
                     .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;
             }
@@ -2960,21 +2950,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.
@@ -2986,7 +2980,7 @@
             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 (DBG) log("updateCarrierDisplayName: rawSpn = " + spn);
             if (!TextUtils.isEmpty(crossSimSpnFormat)) {
                 if (!TextUtils.isEmpty(spn)) {
                     // Show SPN + Cross-SIM Calling If SIM has SPN and SPN display condition
@@ -3045,7 +3039,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;
 
@@ -3057,8 +3051,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;
             }
 
@@ -3066,20 +3060,21 @@
                 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();
     }
 
     /**
@@ -3288,7 +3283,7 @@
         mImsRegistrationOnOff = registered;
 
         // It's possible ServiceState changes did not trigger SPN display update; we update it here.
-        updateSpnDisplay();
+        updateCarrierDisplayName();
     }
 
     public void onImsCapabilityChanged() {
@@ -3702,11 +3697,11 @@
         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();
+            updateCarrierDisplayName();
         }
 
         if (hasChanged) {
@@ -4977,7 +4972,7 @@
      */
     public void powerOffRadioSafely() {
         synchronized (this) {
-            SatelliteController.getInstance().onCellularRadioPowerOffRequested();
+            SatelliteController.getInstance().onSetCellularRadioPowerStateRequested(false);
             if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
                 EmergencyStateTracker.getInstance().onCellularRadioPowerOffRequested();
             }
@@ -5268,11 +5263,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/SignalStrengthController.java b/src/java/com/android/internal/telephony/SignalStrengthController.java
index 98f84b2..31e1950 100644
--- a/src/java/com/android/internal/telephony/SignalStrengthController.java
+++ b/src/java/com/android/internal/telephony/SignalStrengthController.java
@@ -164,9 +164,11 @@
         CarrierConfigManager ccm = mPhone.getContext().getSystemService(CarrierConfigManager.class);
         mCarrierConfig = getCarrierConfig();
         // Callback which directly handle config change should be executed on handler thread
-        ccm.registerCarrierConfigChangeListener(this::post,
-                (slotIndex, subId, carrierId, specificCarrierId) ->
-                        onCarrierConfigurationChanged(slotIndex));
+        if (ccm != null) {
+            ccm.registerCarrierConfigChangeListener(this::post,
+                    (slotIndex, subId, carrierId, specificCarrierId) ->
+                            onCarrierConfigurationChanged(slotIndex));
+        }
 
         mPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null);
     }
diff --git a/src/java/com/android/internal/telephony/SimResponse.java b/src/java/com/android/internal/telephony/SimResponse.java
index 59defc3..97692a0 100644
--- a/src/java/com/android/internal/telephony/SimResponse.java
+++ b/src/java/com/android/internal/telephony/SimResponse.java
@@ -112,7 +112,7 @@
             android.hardware.radio.sim.CarrierRestrictions carrierRestrictions,
             int multiSimPolicy) {
         RILRequest rr = mRil.processResponse(HAL_SERVICE_SIM, responseInfo);
-        boolean carrierLockInfoSupported = mRil.getHalVersion(HAL_SERVICE_SIM).greater(
+        boolean carrierLockInfoSupported = mRil.getHalVersion(HAL_SERVICE_SIM).greaterOrEqual(
                 RIL.RADIO_HAL_VERSION_2_2);
         if (rr == null) {
             return;
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/SmsBroadcastUndelivered.java b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
index 077ee0b..8c9a820 100644
--- a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
+++ b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
@@ -27,6 +27,7 @@
 import android.database.Cursor;
 import android.database.SQLException;
 import android.os.PersistableBundle;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
@@ -35,6 +36,7 @@
 import com.android.internal.telephony.analytics.TelephonyAnalytics;
 import com.android.internal.telephony.analytics.TelephonyAnalytics.SmsMmsAnalytics;
 import com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.gsm.GsmInboundSmsHandler;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
@@ -97,6 +99,10 @@
     /** Content resolver to use to access raw table from SmsProvider. */
     private final ContentResolver mResolver;
 
+    private final UserManager mUserManager;
+
+    private final FeatureFlags mFeatureFlags;
+
     /** Broadcast receiver that processes the raw table when the user unlocks the phone for the
      *  first time after reboot and the credential-encrypted storage is available.
      */
@@ -105,6 +111,12 @@
         public void onReceive(final Context context, Intent intent) {
             Rlog.d(TAG, "Received broadcast " + intent.getAction());
             if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+                if (mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser()) {
+                    int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+                    if (userId != getMainUser().getIdentifier()) {
+                        return;
+                    }
+                }
                 new ScanRawTableThread(context).start();
             }
         }
@@ -126,9 +138,9 @@
     }
 
     public static void initialize(Context context, GsmInboundSmsHandler gsmInboundSmsHandler,
-        CdmaInboundSmsHandler cdmaInboundSmsHandler) {
+        CdmaInboundSmsHandler cdmaInboundSmsHandler, FeatureFlags featureFlags) {
         if (instance == null) {
-            instance = new SmsBroadcastUndelivered(context);
+            instance = new SmsBroadcastUndelivered(context, featureFlags);
         }
 
         // Tell handlers to start processing new messages and transit from the startup state to the
@@ -143,20 +155,38 @@
     }
 
     @UnsupportedAppUsage
-    private SmsBroadcastUndelivered(Context context) {
+    private SmsBroadcastUndelivered(Context context, FeatureFlags featureFlags) {
         mResolver = context.getContentResolver();
 
-        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        mUserManager = context.getSystemService(UserManager.class);
+        mFeatureFlags = featureFlags;
 
-        if (userManager.isUserUnlocked()) {
+        UserHandle mainUser = getMainUser();
+        boolean isUserUnlocked = mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser() ?
+                        mUserManager.isUserUnlocked(mainUser) : mUserManager.isUserUnlocked();
+        if (isUserUnlocked) {
             new ScanRawTableThread(context).start();
         } else {
             IntentFilter userFilter = new IntentFilter();
             userFilter.addAction(Intent.ACTION_USER_UNLOCKED);
-            context.registerReceiver(mBroadcastReceiver, userFilter);
+            if (mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser()) {
+                context.registerReceiverAsUser(
+                        mBroadcastReceiver, mainUser, userFilter, null, null);
+            } else {
+                context.registerReceiver(mBroadcastReceiver, userFilter);
+            }
         }
     }
 
+    /** Returns the MainUser, which is the user designated for sending SMS broadcasts. */
+    private UserHandle getMainUser() {
+        UserHandle mainUser = null;
+        if (mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser()) {
+            mainUser = mUserManager.getMainUser();
+        }
+        return mainUser != null ? mainUser : UserHandle.SYSTEM;
+    }
+
     /**
      * Scan the raw table for complete SMS messages to broadcast, and old PDUs to delete.
      */
@@ -296,7 +326,8 @@
         int subId = SubscriptionManager.getDefaultSmsSubscriptionId();
         CarrierConfigManager configManager =
                 (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        PersistableBundle bundle = configManager.getConfigForSubId(subId);
+        PersistableBundle bundle = null;
+        if (configManager != null) bundle = configManager.getConfigForSubId(subId);
 
         if (bundle != null) {
             return bundle.getLong(CarrierConfigManager.KEY_UNDELIVERED_SMS_MESSAGE_EXPIRATION_TIME,
diff --git a/src/java/com/android/internal/telephony/SmsController.java b/src/java/com/android/internal/telephony/SmsController.java
index 59184d8..e3c409d 100644
--- a/src/java/com/android/internal/telephony/SmsController.java
+++ b/src/java/com/android/internal/telephony/SmsController.java
@@ -176,11 +176,13 @@
         if (callingPackage == null) {
             callingPackage = getCallingPackage();
         }
+        UserHandle callingUser = Binder.getCallingUserHandle();
+
         Rlog.d(LOG_TAG, "sendDataForSubscriber caller=" + callingPackage);
 
         // Check if user is associated with the subscription
         if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
-                Binder.getCallingUserHandle(), destAddr)) {
+                callingUser, destAddr)) {
             TelephonyUtils.showSwitchToManagedProfileDialogIfAppropriate(mContext, subId,
                     Binder.getCallingUid(), callingPackage);
             sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_USER_NOT_ALLOWED);
@@ -195,7 +197,8 @@
 
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendData(callingPackage, callingAttributionTag, destAddr, scAddr, destPort,
+            iccSmsIntMgr.sendData(callingPackage, callingUser.getIdentifier(),
+                    callingAttributionTag, destAddr, scAddr, destPort,
                     data, sentIntent, deliveryIntent);
         } else {
             Rlog.e(LOG_TAG, "sendDataForSubscriber iccSmsIntMgr is null for"
@@ -206,12 +209,14 @@
     }
 
     private void sendDataForSubscriberWithSelfPermissionsInternal(int subId, String callingPackage,
-            String callingAttributionTag, String destAddr, String scAddr, int destPort, byte[] data,
-            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean isForVvm) {
+            int callingUser, String callingAttributionTag, String destAddr, String scAddr,
+            int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
+            boolean isForVvm) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendDataWithSelfPermissions(callingPackage, callingAttributionTag,
-                    destAddr, scAddr, destPort, data, sentIntent, deliveryIntent, isForVvm);
+            iccSmsIntMgr.sendDataWithSelfPermissions(callingPackage, callingUser,
+                    callingAttributionTag, destAddr, scAddr, destPort, data, sentIntent,
+                    deliveryIntent, isForVvm);
         } else {
             Rlog.e(LOG_TAG, "sendText iccSmsIntMgr is null for"
                     + " Subscription: " + subId);
@@ -219,7 +224,15 @@
         }
     }
 
+    @NonNull
     private String getCallingPackage() {
+        if (mFlags.hsumPackageManager()) {
+            PackageManager pm = mContext.createContextAsUser(Binder.getCallingUserHandle(), 0)
+                    .getPackageManager();
+            String[] packages = pm.getPackagesForUid(Binder.getCallingUid());
+            if (packages == null || packages.length == 0) return "";
+            return packages[0];
+        }
         return mContext.getPackageManager().getPackagesForUid(Binder.getCallingUid())[0];
     }
 
@@ -261,6 +274,8 @@
         if (callingPackage == null) {
             callingPackage = getCallingPackage();
         }
+        UserHandle callingUser = Binder.getCallingUserHandle();
+
         Rlog.d(LOG_TAG, "sendTextForSubscriber caller=" + callingPackage);
 
         if (skipFdnCheck || skipShortCodeCheck) {
@@ -306,9 +321,9 @@
             if (isBluetoothSubscription(info)) {
                 sendBluetoothText(info, destAddr, text, sentIntent, deliveryIntent);
             } else {
-                sendIccText(subId, callingPackage, destAddr, scAddr, text, sentIntent,
-                        deliveryIntent, persistMessageForNonDefaultSmsApp, messageId,
-                        skipShortCodeCheck);
+                sendIccText(subId, callingPackage, callingUser.getIdentifier(), destAddr, scAddr,
+                        text, sentIntent, deliveryIntent, persistMessageForNonDefaultSmsApp,
+                        messageId, skipShortCodeCheck);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -326,14 +341,14 @@
         btSmsInterfaceManager.sendText(mContext, destAddr, text, sentIntent, deliveryIntent, info);
     }
 
-    private void sendIccText(int subId, String callingPackage, String destAddr,
+    private void sendIccText(int subId, String callingPackage, int callingUser, String destAddr,
             String scAddr, String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
             boolean persistMessageForNonDefaultSmsApp, long messageId, boolean skipShortCodeCheck) {
         Rlog.d(LOG_TAG, "sendTextForSubscriber iccSmsIntMgr"
                 + " Subscription: " + subId + " " + formatCrossStackMessageId(messageId));
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendText(callingPackage, destAddr, scAddr, text, sentIntent,
+            iccSmsIntMgr.sendText(callingPackage, callingUser, destAddr, scAddr, text, sentIntent,
                     deliveryIntent, persistMessageForNonDefaultSmsApp, messageId,
                     skipShortCodeCheck);
         } else {
@@ -344,13 +359,14 @@
     }
 
     private void sendTextForSubscriberWithSelfPermissionsInternal(int subId, String callingPackage,
-            String callingAttributeTag, String destAddr, String scAddr, String text,
-            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean persistMessage,
-            boolean isForVvm) {
+            int callingUser, String callingAttributeTag, String destAddr, String scAddr,
+            String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
+            boolean persistMessage, boolean isForVvm) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendTextWithSelfPermissions(callingPackage, callingAttributeTag, destAddr,
-                    scAddr, text, sentIntent, deliveryIntent, persistMessage, isForVvm);
+            iccSmsIntMgr.sendTextWithSelfPermissions(callingPackage, callingUser,
+                    callingAttributeTag, destAddr, scAddr, text, sentIntent, deliveryIntent,
+                    persistMessage, isForVvm);
         } else {
             Rlog.e(LOG_TAG, "sendText iccSmsIntMgr is null for"
                     + " Subscription: " + subId);
@@ -366,6 +382,8 @@
         if (callingPackage == null) {
             callingPackage = getCallingPackage();
         }
+        UserHandle callingUser = Binder.getCallingUserHandle();
+
         Rlog.d(LOG_TAG, "sendTextForSubscriberWithOptions caller=" + callingPackage);
 
         // Check if user is associated with the subscription
@@ -385,9 +403,9 @@
 
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendTextWithOptions(callingPackage, callingAttributionTag, destAddr,
-                    scAddr, parts, sentIntent, deliveryIntent, persistMessage, priority, expectMore,
-                    validityPeriod);
+            iccSmsIntMgr.sendTextWithOptions(callingPackage, callingUser.getIdentifier(),
+                    callingAttributionTag, destAddr, scAddr, parts, sentIntent, deliveryIntent,
+                    persistMessage, priority, expectMore, validityPeriod);
         } else {
             Rlog.e(LOG_TAG, "sendTextWithOptions iccSmsIntMgr is null for"
                     + " Subscription: " + subId);
@@ -402,9 +420,9 @@
             boolean persistMessageForNonDefaultSmsApp, long messageId) {
         // This is different from the checking of other method. It prefers the package name
         // returned by getCallPackage() for backward-compatibility.
-        if (getCallingPackage() != null) {
-            callingPackage = getCallingPackage();
-        }
+        callingPackage = getCallingPackage();
+        UserHandle callingUser = Binder.getCallingUserHandle();
+
         Rlog.d(LOG_TAG, "sendMultipartTextForSubscriber caller=" + callingPackage);
 
         // Check if user is associated with the subscription
@@ -426,9 +444,9 @@
 
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendMultipartText(callingPackage, callingAttributionTag, destAddr, scAddr,
-                    parts, sentIntents, deliveryIntents, persistMessageForNonDefaultSmsApp,
-                    messageId);
+            iccSmsIntMgr.sendMultipartText(callingPackage, callingUser.getIdentifier(),
+                    callingAttributionTag, destAddr, scAddr, parts, sentIntents, deliveryIntents,
+                    persistMessageForNonDefaultSmsApp, messageId);
         } else {
             Rlog.e(LOG_TAG, "sendMultipartTextForSubscriber iccSmsIntMgr is null for"
                     + " Subscription: " + subId + " " + formatCrossStackMessageId(messageId));
@@ -444,6 +462,8 @@
         if (callingPackage == null) {
             callingPackage = getCallingPackage();
         }
+        UserHandle callingUser = Binder.getCallingUserHandle();
+
         Rlog.d(LOG_TAG, "sendMultipartTextForSubscriberWithOptions caller=" + callingPackage);
 
         // Check if user is associated with the subscription
@@ -463,9 +483,9 @@
 
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendMultipartTextWithOptions(callingPackage, callingAttributionTag,
-                    destAddr, scAddr, parts, sentIntents, deliveryIntents, persistMessage, priority,
-                    expectMore, validityPeriod, 0L /* messageId */);
+            iccSmsIntMgr.sendMultipartTextWithOptions(callingPackage, callingUser.getIdentifier(),
+                    callingAttributionTag, destAddr, scAddr, parts, sentIntents, deliveryIntents,
+                    persistMessage, priority, expectMore, validityPeriod, 0L /* messageId */);
         } else {
             Rlog.e(LOG_TAG, "sendMultipartTextWithOptions iccSmsIntMgr is null for"
                     + " Subscription: " + subId);
@@ -717,6 +737,7 @@
             Uri messageUri, String scAddress, PendingIntent sentIntent,
             PendingIntent deliveryIntent) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        UserHandle callingUser = Binder.getCallingUserHandle();
         if (!getCallingPackage().equals(callingPkg)) {
             throw new SecurityException("sendStoredText: Package " + callingPkg
                     + "does not belong to " + Binder.getCallingUid());
@@ -724,8 +745,8 @@
         Rlog.d(LOG_TAG, "sendStoredText caller=" + callingPkg);
 
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendStoredText(callingPkg, callingAttributionTag, messageUri, scAddress,
-                    sentIntent, deliveryIntent);
+            iccSmsIntMgr.sendStoredText(callingPkg, callingUser.getIdentifier(),
+                    callingAttributionTag, messageUri, scAddress, sentIntent, deliveryIntent);
         } else {
             Rlog.e(LOG_TAG, "sendStoredText iccSmsIntMgr is null for subscription: " + subId);
             sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
@@ -737,6 +758,8 @@
             Uri messageUri, String scAddress, List<PendingIntent> sentIntents,
             List<PendingIntent> deliveryIntents) {
         IccSmsInterfaceManager iccSmsIntMgr = getIccSmsInterfaceManager(subId);
+        UserHandle callingUser = Binder.getCallingUserHandle();
+
         if (!getCallingPackage().equals(callingPkg)) {
             throw new SecurityException("sendStoredMultipartText: Package " + callingPkg
                     + " does not belong to " + Binder.getCallingUid());
@@ -744,8 +767,8 @@
         Rlog.d(LOG_TAG, "sendStoredMultipartText caller=" + callingPkg);
 
         if (iccSmsIntMgr != null) {
-            iccSmsIntMgr.sendStoredMultipartText(callingPkg, callingAttributionTag, messageUri,
-                    scAddress, sentIntents, deliveryIntents);
+            iccSmsIntMgr.sendStoredMultipartText(callingPkg, callingUser.getIdentifier(),
+                    callingAttributionTag, messageUri, scAddress, sentIntents, deliveryIntents);
         } else {
             Rlog.e(LOG_TAG, "sendStoredMultipartText iccSmsIntMgr is null for subscription: "
                     + subId);
@@ -979,7 +1002,7 @@
      * Internal API to send visual voicemail related SMS. This is not exposed outside the phone
      * process, and should be called only after verifying that the caller is the default VVM app.
      */
-    public void sendVisualVoicemailSmsForSubscriber(String callingPackage,
+    public void sendVisualVoicemailSmsForSubscriber(String callingPackage, int callingUser,
             String callingAttributionTag, int subId, String number, int port, String text,
             PendingIntent sentIntent) {
         Rlog.d(LOG_TAG, "sendVisualVoicemailSmsForSubscriber caller=" + callingPackage);
@@ -1001,12 +1024,12 @@
         }
 
         if (port == 0) {
-            sendTextForSubscriberWithSelfPermissionsInternal(subId, callingPackage,
+            sendTextForSubscriberWithSelfPermissionsInternal(subId, callingPackage, callingUser,
                     callingAttributionTag, number, null, text, sentIntent, null, false,
                     true /* isForVvm */);
         } else {
             byte[] data = text.getBytes(StandardCharsets.UTF_8);
-            sendDataForSubscriberWithSelfPermissionsInternal(subId, callingPackage,
+            sendDataForSubscriberWithSelfPermissionsInternal(subId, callingPackage, callingUser,
                     callingAttributionTag, number, null, (short) port, data, sentIntent, null,
                     true /* isForVvm */);
         }
diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java
index bc1e1a8..023680a 100644
--- a/src/java/com/android/internal/telephony/SmsDispatchersController.java
+++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java
@@ -29,12 +29,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
 import android.net.Uri;
 import android.os.AsyncResult;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Telephony.Sms;
 import android.provider.Telephony.Sms.Intents;
@@ -45,7 +47,9 @@
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
 import android.telephony.TelephonyManager;
+import android.telephony.SubscriptionManager;
 import android.telephony.emergency.EmergencyNumber;
+import android.telephony.satellite.SatelliteManager;
 import android.text.TextUtils;
 
 import com.android.ims.ImsManager;
@@ -61,11 +65,15 @@
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.gsm.GsmInboundSmsHandler;
 import com.android.internal.telephony.gsm.GsmSMSDispatcher;
+import com.android.internal.telephony.satellite.DatagramDispatcher;
+import com.android.internal.telephony.satellite.SatelliteController;
+import com.android.internal.R;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -115,6 +123,12 @@
     /** Called when the domain selection should be performed. */
     private static final int EVENT_REQUEST_DOMAIN_SELECTION = 22;
 
+    /** Called when {@link DatagramDispatcher} informs to send carrier roaming nb iot ntn sms. */
+    private static final int CMD_SEND_TEXT = 23;
+
+    /** Called when {@link DatagramDispatcher} informs sms cannot be sent over ntn due to error. */
+    private static final int EVENT_SEND_TEXT_OVER_NTN_ERROR = 24;
+
     /** Delete any partial message segments after being IN_SERVICE for 1 day. */
     private static final long PARTIAL_SEGMENT_WAIT_DURATION = (long) (60 * 60 * 1000) * 24;
     /** Constant for invalid time */
@@ -205,15 +219,17 @@
             };
 
     /** Stores the sending SMS information for a pending request. */
-    private static class PendingRequest {
+    public static class PendingRequest {
         public static final int TYPE_DATA = 1;
         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;
         public final String callingPackage;
+        public final int callingUser;
         public final String destAddr;
         public final String scAddr;
         public final ArrayList<PendingIntent> sentIntents;
@@ -231,16 +247,20 @@
         public final int validityPeriod;
         public final long messageId;
         public final boolean skipShortCodeCheck;
+        public final long uniqueMessageId;
+        public final boolean isMtSmsPolling;
 
-        PendingRequest(int type, SMSDispatcher.SmsTracker tracker, String callingPackage,
-                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) {
+        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,
+                boolean isMtSmsPolling) {
             this.type = type;
             this.tracker = tracker;
             this.callingPackage = callingPackage;
+            this.callingUser = callingUser;
             this.destAddr = destAddr;
             this.scAddr = scAddr;
             this.sentIntents = sentIntents;
@@ -258,6 +278,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));
         }
     }
 
@@ -381,12 +412,12 @@
         mImsSmsDispatcher = new ImsSmsDispatcher(phone, this, ImsManager::getConnector);
         mCdmaDispatcher = new CdmaSMSDispatcher(phone, this);
         mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(phone.getContext(),
-                storageMonitor, phone, looper);
+                storageMonitor, phone, looper, mFeatureFlags);
         mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(phone.getContext(),
-                storageMonitor, phone, (CdmaSMSDispatcher) mCdmaDispatcher, looper);
+                storageMonitor, phone, (CdmaSMSDispatcher) mCdmaDispatcher, looper, mFeatureFlags);
         mGsmDispatcher = new GsmSMSDispatcher(phone, this, mGsmInboundSmsHandler);
         SmsBroadcastUndelivered.initialize(phone.getContext(),
-                mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
+                mGsmInboundSmsHandler, mCdmaInboundSmsHandler, mFeatureFlags);
         InboundSmsHandler.registerNewMessageNotificationActionHandler(phone.getContext());
 
         mCi.registerForOn(this, EVENT_RADIO_ON, null);
@@ -525,6 +556,26 @@
                 }
                 break;
             }
+            case CMD_SEND_TEXT: {
+                PendingRequest request = (PendingRequest) msg.obj;
+                if (request.type == PendingRequest.TYPE_TEXT) {
+                    sendTextInternal(request);
+                } else if (request.type == PendingRequest.TYPE_MULTIPART_TEXT) {
+                    sendMultipartTextInternal(request);
+                } else {
+                    logd("CMD_SEND_TEXT: type=" + request.type
+                            + " messageId=" + request.messageId);
+                }
+                break;
+            }
+            case EVENT_SEND_TEXT_OVER_NTN_ERROR: {
+                PendingRequest request = (PendingRequest) msg.obj;
+                logd("EVENT_SEND_TEXT_OVER_NTN_ERROR: type=" + request.type
+                        + " messageId=" + request.messageId);
+                triggerSentIntentForFailure(request.sentIntents);
+                break;
+            }
+
             default:
                 if (isCdmaMo()) {
                     mCdmaDispatcher.handleMessage(msg);
@@ -786,8 +837,10 @@
                 SomeArgs args = SomeArgs.obtain();
                 args.arg1 = getDomainSelectionConnectionHolder(isEmergency);
                 args.arg2 = new PendingRequest(PendingRequest.TYPE_RETRY_SMS, tracker,
-                        null, null, null, null, null, false, null, 0, null, null, false,
-                        0, false, 0, 0L, false);
+                        null, UserHandle.USER_NULL, null, null,
+                        null, null, false, null, 0,
+                        null, null, false,
+                        0, false, 0, 0L, false, false);
                 args.arg3 = "sendRetrySms";
                 sendMessage(obtainMessage(EVENT_REQUEST_DOMAIN_SELECTION, args));
                 return;
@@ -833,8 +886,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);
-                notifySmsSentFailedToEmergencyStateTracker(
-                        tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService);
+                notifySmsSent(tracker, !retryUsingImsService,
+                        true /*isLastSmsPart*/, false /*success*/);
                 return;
             }
             String scAddr = (String) map.get("scAddr");
@@ -842,8 +895,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);
-                notifySmsSentFailedToEmergencyStateTracker(
-                        tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService);
+                notifySmsSent(tracker, !retryUsingImsService,
+                        true /*isLastSmsPart*/, false /*success*/);
                 return;
             }
 
@@ -884,8 +937,8 @@
                         + "scAddr: %s, "
                         + "destPort: %s", scAddr, map.get("destPort")));
                 tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE);
-                notifySmsSentFailedToEmergencyStateTracker(
-                        tracker.mDestAddress, tracker.mMessageId, !retryUsingImsService);
+                notifySmsSent(tracker, !retryUsingImsService,
+                    true /*isLastSmsPart*/, false /*success*/);
                 return;
             }
             // replace old smsc and pdu with newly encoded ones
@@ -1178,36 +1231,35 @@
     }
 
     /**
-     * Called when MO SMS is successfully sent.
+     * Called when MO SMS is sent.
      */
-    protected void notifySmsSentToEmergencyStateTracker(@NonNull String destAddr, long messageId,
-            boolean isOverIms, boolean isLastSmsPart) {
+    protected void notifySmsSent(@NonNull SMSDispatcher.SmsTracker tracker,
+            boolean isOverIms, boolean isLastSmsPart, boolean success) {
+        notifySmsSentToEmergencyStateTracker(tracker.mDestAddress,
+            tracker.mMessageId, isOverIms, isLastSmsPart, success);
+        notifySmsSentToDatagramDispatcher(tracker.mUniqueMessageId, success);
+    }
+
+    /**
+     * Called when MO SMS is sent.
+     */
+    private void notifySmsSentToEmergencyStateTracker(@NonNull String destAddr, long messageId,
+            boolean isOverIms, boolean isLastSmsPart, boolean success) {
         if (isSmsDomainSelectionEnabled()) {
             // Run on main thread for interworking with EmergencyStateTracker.
             SomeArgs args = SomeArgs.obtain();
             args.arg1 = destAddr;
             args.arg2 = Long.valueOf(messageId);
-            args.arg3 = Boolean.TRUE;
+            args.arg3 = Boolean.valueOf(success);
             args.arg4 = Boolean.valueOf(isOverIms);
             args.arg5 = Boolean.valueOf(isLastSmsPart);
             sendMessage(obtainMessage(EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION, args));
         }
     }
 
-    /**
-     * Called when sending MO SMS is failed.
-     */
-    protected void notifySmsSentFailedToEmergencyStateTracker(@NonNull String destAddr,
-            long messageId, boolean isOverIms) {
-        if (isSmsDomainSelectionEnabled()) {
-            // Run on main thread for interworking with EmergencyStateTracker.
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = destAddr;
-            args.arg2 = Long.valueOf(messageId);
-            args.arg3 = Boolean.FALSE;
-            args.arg4 = Boolean.valueOf(isOverIms);
-            args.arg5 = Boolean.TRUE; // Ignored when sending SMS is failed.
-            sendMessage(obtainMessage(EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION, args));
+    private void notifySmsSentToDatagramDispatcher(long messageId, boolean success) {
+        if (SatelliteController.getInstance().isInCarrierRoamingNbIotNtn(mPhone)) {
+            DatagramDispatcher.getInstance().onSendSmsDone(mPhone.getSubId(), messageId, success);
         }
     }
 
@@ -1367,17 +1419,20 @@
     private void sendData(@NetworkRegistrationInfo.Domain int domain,
             @NonNull PendingRequest request) {
         if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
-            mImsSmsDispatcher.sendData(request.callingPackage, request.destAddr, request.scAddr,
-                    request.destPort, request.data, request.sentIntents.get(0),
-                    request.deliveryIntents.get(0), request.isForVvm);
+            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.uniqueMessageId);
         } else if (isCdmaMo(domain)) {
-            mCdmaDispatcher.sendData(request.callingPackage, request.destAddr, request.scAddr,
-                    request.destPort, request.data, request.sentIntents.get(0),
-                    request.deliveryIntents.get(0), request.isForVvm);
+            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.uniqueMessageId);
         } else {
-            mGsmDispatcher.sendData(request.callingPackage, request.destAddr, request.scAddr,
-                    request.destPort, request.data, request.sentIntents.get(0),
-                    request.deliveryIntents.get(0), request.isForVvm);
+            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.uniqueMessageId);
         }
     }
 
@@ -1394,22 +1449,25 @@
         if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
             mImsSmsDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0),
                     request.sentIntents.get(0), request.deliveryIntents.get(0),
-                    request.messageUri, request.callingPackage, request.persistMessage,
-                    request.priority, false /*request.expectMore*/, request.validityPeriod,
-                    request.isForVvm, request.messageId, request.skipShortCodeCheck);
+                    request.messageUri, request.callingPackage, request.callingUser,
+                    request.persistMessage, request.priority,  /*request.expectMore*/ false,
+                    request.validityPeriod, request.isForVvm, request.messageId,
+                    request.skipShortCodeCheck, request.uniqueMessageId);
         } else {
             if (isCdmaMo(domain)) {
                 mCdmaDispatcher.sendText(request.destAddr, request.scAddr, request.texts.get(0),
                         request.sentIntents.get(0), request.deliveryIntents.get(0),
-                        request.messageUri, request.callingPackage, request.persistMessage,
-                        request.priority, request.expectMore, request.validityPeriod,
-                        request.isForVvm, request.messageId, request.skipShortCodeCheck);
+                        request.messageUri, request.callingPackage, request.callingUser,
+                        request.persistMessage, request.priority, request.expectMore,
+                        request.validityPeriod, request.isForVvm, request.messageId,
+                        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.persistMessage,
-                        request.priority, request.expectMore, request.validityPeriod,
-                        request.isForVvm, request.messageId, request.skipShortCodeCheck);
+                        request.messageUri, request.callingPackage, request.callingUser,
+                        request.persistMessage, request.priority, request.expectMore,
+                        request.validityPeriod, request.isForVvm, request.messageId,
+                        request.skipShortCodeCheck, request.uniqueMessageId);
             }
         }
     }
@@ -1427,24 +1485,31 @@
         if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
             mImsSmsDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts,
                     request.sentIntents, request.deliveryIntents, request.messageUri,
-                    request.callingPackage, request.persistMessage, request.priority,
-                    false /*request.expectMore*/, request.validityPeriod, request.messageId);
+                    request.callingPackage, request.callingUser, request.persistMessage,
+                    request.priority, false /*request.expectMore*/, request.validityPeriod,
+                    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.persistMessage, request.priority,
-                        request.expectMore, request.validityPeriod, request.messageId);
+                        request.callingPackage, request.callingUser, request.persistMessage,
+                        request.priority, request.expectMore, request.validityPeriod,
+                        request.messageId, request.uniqueMessageId);
             } else {
                 mGsmDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts,
                         request.sentIntents, request.deliveryIntents, request.messageUri,
-                        request.callingPackage, request.persistMessage, request.priority,
-                        request.expectMore, request.validityPeriod, request.messageId);
+                        request.callingPackage, request.callingUser, request.persistMessage,
+                        request.priority, request.expectMore, request.validityPeriod,
+                        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) {
@@ -1452,7 +1517,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);
         }
@@ -1546,31 +1615,36 @@
      *  broadcast when the message is delivered to the recipient.  The
      *  raw pdu of the status report is in the extended data ("pdu").
      */
-    protected void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
-            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent, boolean isForVvm) {
+    protected void sendData(String callingPackage, int callingUser, String destAddr, String scAddr,
+            int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
+            boolean isForVvm) {
         if (TextUtils.isEmpty(scAddr)) {
             scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPackage);
         }
 
         if (isSmsDomainSelectionEnabled()) {
             sendSmsUsingDomainSelection(getDomainSelectionConnectionHolder(false),
-                    new PendingRequest(PendingRequest.TYPE_DATA, null, callingPackage,
+                    new PendingRequest(PendingRequest.TYPE_DATA, null, callingPackage, callingUser,
                             destAddr, scAddr, asArrayList(sentIntent),
-                            asArrayList(deliveryIntent), isForVvm, data, destPort, null, null,
-                            false, 0, false, 0, 0L, false),
+                            asArrayList(deliveryIntent), isForVvm, data, destPort, null,
+                            null, false, 0, false, 0,
+                            0L, false, false),
                     "sendData");
             return;
         }
 
         if (mImsSmsDispatcher.isAvailable()) {
-            mImsSmsDispatcher.sendData(callingPackage, destAddr, scAddr, destPort, data, sentIntent,
-                    deliveryIntent, isForVvm);
+            mImsSmsDispatcher.sendData(callingPackage, callingUser, destAddr, scAddr, destPort,
+                    data, sentIntent, deliveryIntent, isForVvm,
+                    PendingRequest.getNextUniqueMessageId());
         } else if (isCdmaMo()) {
-            mCdmaDispatcher.sendData(callingPackage, destAddr, scAddr, destPort, data, sentIntent,
-                    deliveryIntent, isForVvm);
+            mCdmaDispatcher.sendData(callingPackage, callingUser, destAddr, scAddr, destPort, data,
+                    sentIntent, deliveryIntent, isForVvm,
+                    PendingRequest.getNextUniqueMessageId());
         } else {
-            mGsmDispatcher.sendData(callingPackage, destAddr, scAddr, destPort, data, sentIntent,
-                    deliveryIntent, isForVvm);
+            mGsmDispatcher.sendData(callingPackage, callingUser, destAddr, scAddr, destPort, data,
+                    sentIntent, deliveryIntent, isForVvm,
+                    PendingRequest.getNextUniqueMessageId());
         }
     }
 
@@ -1672,11 +1746,12 @@
      *  Any Other values included Negative considered as Invalid Validity Period of the message.
      */
     public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
-            PendingIntent deliveryIntent, Uri messageUri, String callingPkg, boolean persistMessage,
-            int priority, boolean expectMore, int validityPeriod, boolean isForVvm,
-            long messageId) {
+            PendingIntent deliveryIntent, Uri messageUri, String callingPkg, int callingUser,
+            boolean persistMessage, int priority, boolean expectMore, int validityPeriod,
+            boolean isForVvm, long messageId) {
         sendText(destAddr, scAddr, text, sentIntent, deliveryIntent, messageUri, callingPkg,
-                persistMessage, priority, expectMore, validityPeriod, isForVvm, messageId, false);
+                callingUser, persistMessage, priority, expectMore, validityPeriod, isForVvm,
+                messageId, false);
     }
 
     /**
@@ -1778,39 +1853,62 @@
      * @param skipShortCodeCheck Skip check for short code type destination address.
      */
     public void sendText(String destAddr, String scAddr, String text, PendingIntent sentIntent,
-            PendingIntent deliveryIntent, Uri messageUri, String callingPkg, boolean persistMessage,
-            int priority, boolean expectMore, int validityPeriod, boolean isForVvm,
-            long messageId, boolean skipShortCodeCheck) {
+            PendingIntent deliveryIntent, Uri messageUri, String callingPkg, int callingUser,
+            boolean persistMessage, int priority, boolean expectMore, int validityPeriod,
+            boolean isForVvm, long messageId, boolean skipShortCodeCheck) {
         if (TextUtils.isEmpty(scAddr)) {
             scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPkg);
         }
 
-        if (isSmsDomainSelectionEnabled()) {
-            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-            boolean isEmergency = tm.isEmergencyNumber(destAddr);
-            sendSmsUsingDomainSelection(getDomainSelectionConnectionHolder(isEmergency),
-                    new PendingRequest(PendingRequest.TYPE_TEXT, null, callingPkg,
-                            destAddr, scAddr, asArrayList(sentIntent),
-                            asArrayList(deliveryIntent), isForVvm, null, 0, asArrayList(text),
-                            messageUri, persistMessage, priority, expectMore, validityPeriod,
-                            messageId, skipShortCodeCheck),
-                    "sendText");
+        PendingRequest pendingRequest = new PendingRequest(PendingRequest.TYPE_TEXT, null,
+                callingPkg, callingUser, destAddr, scAddr, asArrayList(sentIntent),
+                asArrayList(deliveryIntent), isForVvm, null, 0, asArrayList(text),
+                messageUri, persistMessage, priority, expectMore, validityPeriod, messageId,
+                skipShortCodeCheck, false);
+
+        if (SatelliteController.getInstance().isInCarrierRoamingNbIotNtn(mPhone)) {
+            // Send P2P SMS using carrier roaming NB IOT NTN
+            DatagramDispatcher.getInstance().sendSms(pendingRequest);
             return;
         }
 
-        if (mImsSmsDispatcher.isAvailable() || mImsSmsDispatcher.isEmergencySmsSupport(destAddr)) {
-            mImsSmsDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
-                    messageUri, callingPkg, persistMessage, priority, false /*expectMore*/,
-                    validityPeriod, isForVvm, messageId, skipShortCodeCheck);
+        sendTextInternal(pendingRequest);
+    }
+
+    private void sendTextInternal(PendingRequest request) {
+        logd("sendTextInternal: messageId=" + request.messageId
+                 + ", uniqueMessageId=" + request.uniqueMessageId);
+        if (isSmsDomainSelectionEnabled()) {
+            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            boolean isEmergency = tm.isEmergencyNumber(request.destAddr);
+            sendSmsUsingDomainSelection(getDomainSelectionConnectionHolder(isEmergency),
+                    request, "sendText");
+            return;
+        }
+
+        if (mImsSmsDispatcher.isAvailable() || mImsSmsDispatcher.isEmergencySmsSupport(
+                request.destAddr)) {
+            mImsSmsDispatcher.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, false /*expectMore*/,
+                    request.validityPeriod, request.isForVvm, request.messageId,
+                    request.skipShortCodeCheck, request.uniqueMessageId);
         } else {
             if (isCdmaMo()) {
-                mCdmaDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
-                        messageUri, callingPkg, persistMessage, priority, expectMore,
-                        validityPeriod, isForVvm, messageId, skipShortCodeCheck);
+                mCdmaDispatcher.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.uniqueMessageId);
             } else {
-                mGsmDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
-                        messageUri, callingPkg, persistMessage, priority, expectMore,
-                        validityPeriod, isForVvm, messageId, skipShortCodeCheck);
+                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.uniqueMessageId);
             }
         }
     }
@@ -1922,37 +2020,55 @@
     protected void sendMultipartText(String destAddr, String scAddr,
             ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
             ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
-            boolean persistMessage, int priority, boolean expectMore, int validityPeriod,
-            long messageId) {
+            int callingUser, boolean persistMessage, int priority, boolean expectMore,
+            int validityPeriod, long messageId) {
         if (TextUtils.isEmpty(scAddr)) {
             scAddr = getSmscAddressFromUSIMWithPhoneIdentity(callingPkg);
         }
 
+        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, false);
+
+        if (SatelliteController.getInstance().isInCarrierRoamingNbIotNtn(mPhone)) {
+            // Send multipart P2P SMS using carrier roaming NB IOT NTN
+            DatagramDispatcher.getInstance().sendSms(pendingRequest);
+            return;
+        }
+
+        sendMultipartTextInternal(pendingRequest);
+    }
+
+    private void sendMultipartTextInternal(PendingRequest request) {
+        logd("sendMultipartTextInternal: messageId=" + request.messageId);
         if (isSmsDomainSelectionEnabled()) {
             TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
-            boolean isEmergency = tm.isEmergencyNumber(destAddr);
+            boolean isEmergency = tm.isEmergencyNumber(request.destAddr);
             sendSmsUsingDomainSelection(getDomainSelectionConnectionHolder(isEmergency),
-                    new PendingRequest(PendingRequest.TYPE_MULTIPART_TEXT, null,
-                            callingPkg, destAddr, scAddr, sentIntents, deliveryIntents, false,
-                            null, 0, parts, messageUri, persistMessage, priority, expectMore,
-                            validityPeriod, messageId, false),
-                    "sendMultipartText");
+                    request, "sendMultipartText");
             return;
         }
 
         if (mImsSmsDispatcher.isAvailable()) {
-            mImsSmsDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents,
-                    deliveryIntents, messageUri, callingPkg, persistMessage, priority,
-                    false /*expectMore*/, validityPeriod, messageId);
+            mImsSmsDispatcher.sendMultipartText(request.destAddr, request.scAddr, request.texts,
+                    request.sentIntents, request.deliveryIntents, request.messageUri,
+                    request.callingPackage, request.callingUser, request.persistMessage,
+                    request.priority, false /*expectMore*/, request.validityPeriod,
+                    request.messageId, request.uniqueMessageId);
         } else {
             if (isCdmaMo()) {
-                mCdmaDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents,
-                        deliveryIntents, messageUri, callingPkg, persistMessage, priority,
-                        expectMore, validityPeriod, messageId);
+                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.uniqueMessageId);
             } else {
-                mGsmDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents,
-                        deliveryIntents, messageUri, callingPkg, persistMessage, priority,
-                        expectMore, validityPeriod, messageId);
+                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.uniqueMessageId);
             }
         }
     }
@@ -2081,6 +2197,80 @@
         else return mGsmInboundSmsHandler;
     }
 
+    /**
+     * This API should be used only by {@link DatagramDispatcher} to send SMS over
+     * non-terrestrial network.
+     *
+     * @param request {@link PendingRequest} object that contains all the information required to
+     *                send MO SMS.
+     */
+    public void sendCarrierRoamingNbIotNtnText(@NonNull PendingRequest request) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            logd("sendCarrierRoamingNbIotNtnText: carrier roaming nb iot ntn "
+                    + "feature flag is disabled");
+            return;
+        }
+
+        sendMessage(obtainMessage(CMD_SEND_TEXT, request));
+    }
+
+    /**
+     * Send error code to pending MO SMS request.
+     *
+     * @param pendingRequest {@link PendingRequest} object that contains all the information
+     *                       related to MO SMS.
+     * @param errorCode error code to be returned.
+     */
+    public void onSendCarrierRoamingNbIotNtnTextError(@NonNull PendingRequest pendingRequest,
+            @SatelliteManager.SatelliteResult int errorCode) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            logd("onSendCarrierRoamingNbIotNtnTextError: carrier roaming nb iot ntn "
+                    + "feature flag is disabled");
+            return;
+        }
+
+        logd("onSendCarrierRoamingNbIotNtnTextError: messageId=" + pendingRequest.messageId
+                + " errorCode=" + errorCode);
+        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().isInCarrierRoamingNbIotNtn(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);
+
+        DatagramDispatcher.getInstance().sendSms(pendingRequest);
+    }
+
     public interface SmsInjectionCallback {
         void onSmsInjectedResult(int result);
     }
diff --git a/src/java/com/android/internal/telephony/SmsStorageMonitor.java b/src/java/com/android/internal/telephony/SmsStorageMonitor.java
index 2736f7a..07b7a18 100644
--- a/src/java/com/android/internal/telephony/SmsStorageMonitor.java
+++ b/src/java/com/android/internal/telephony/SmsStorageMonitor.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -33,6 +34,7 @@
 import android.telephony.SubscriptionManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
 
@@ -91,6 +93,9 @@
 
     private boolean mMemoryStatusOverrideFlag = false;
 
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
+
     /**
      * Hold the wake lock for 5 seconds, which should be enough time for
      * any receiver(s) to grab its own wake lock.
@@ -100,9 +105,11 @@
     /**
      * Creates an SmsStorageMonitor and registers for events.
      * @param phone the Phone to use
+     * @param flags The Android feature flags
      */
-    public SmsStorageMonitor(Phone phone) {
+    public SmsStorageMonitor(@NonNull Phone phone, @NonNull FeatureFlags flags) {
         mPhone = phone;
+        mFeatureFlags = flags;
         mContext = phone.getContext();
         mCi = phone.mCi;
 
@@ -278,7 +285,12 @@
         intent.setComponent(componentName);
         mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
-        mContext.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
+        if (mFeatureFlags.hsumBroadcast()) {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                    android.Manifest.permission.RECEIVE_SMS);
+        } else {
+            mContext.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
+        }
     }
 
     /** Returns whether or not there is storage available for an incoming SMS. */
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index 0b0f9d3..10e97b6 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -289,8 +289,16 @@
         return new GsmCdmaCallTracker(phone, featureFlags);
     }
 
-    public SmsStorageMonitor makeSmsStorageMonitor(Phone phone) {
-        return new SmsStorageMonitor(phone);
+    /**
+     * Create {@link SmsStorageMonitor} instance.
+     *
+     * @param phone Phone instance
+     * @param flags Android feature flags
+     *
+     * @return The created instance
+     */
+    public SmsStorageMonitor makeSmsStorageMonitor(Phone phone, @NonNull FeatureFlags flags) {
+        return new SmsStorageMonitor(phone, flags);
     }
 
     public SmsUsageMonitor makeSmsUsageMonitor(Context context) {
@@ -331,8 +339,16 @@
         return new CarrierActionAgent(phone);
     }
 
-    public CarrierResolver makeCarrierResolver(Phone phone) {
-        return new CarrierResolver(phone);
+    /**
+     * Create {@link CarrierResolver} instance
+     *
+     * @param phone The phone instance
+     * @param flags Android feature flags
+     *
+     * @return The created instance
+     */
+    public CarrierResolver makeCarrierResolver(Phone phone, FeatureFlags flags) {
+        return new CarrierResolver(phone, flags);
     }
 
     public IccPhoneBookInterfaceManager makeIccPhoneBookInterfaceManager(Phone phone) {
diff --git a/src/java/com/android/internal/telephony/TelephonyCountryDetector.java b/src/java/com/android/internal/telephony/TelephonyCountryDetector.java
index 56e8b46..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;
@@ -35,6 +34,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RegistrantList;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.telephony.Rlog;
@@ -42,6 +42,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -83,6 +84,8 @@
     @NonNull private final Geocoder mGeocoder;
     @NonNull private final LocationManager mLocationManager;
     @NonNull private final ConnectivityManager mConnectivityManager;
+    @NonNull private final RegistrantList mWifiConnectivityStateChangedRegistrantList =
+            new RegistrantList();
     @NonNull private final Object mLock = new Object();
     @NonNull
     @GuardedBy("mLock")
@@ -106,6 +109,11 @@
     private Map<String, Long> mOverriddenCachedNetworkCountryCodes = new HashMap<>();
     @GuardedBy("mLock")
     private boolean mIsCountryCodesOverridden = false;
+    private final RegistrantList mCountryCodeChangedRegistrants = new RegistrantList();
+    private boolean mIsWifiNetworkConnected = false;
+
+    private FeatureFlags mFeatureFlags = null;
+
     @NonNull private final LocationListener mLocationListener = new LocationListener() {
         @Override
         public void onLocationChanged(Location location) {
@@ -183,22 +191,26 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected TelephonyCountryDetector(@NonNull Looper looper, @NonNull Context context,
             @NonNull LocationManager locationManager,
-            @NonNull ConnectivityManager connectivityManager) {
+            @NonNull ConnectivityManager connectivityManager,
+            FeatureFlags featureFlags) {
         super(looper);
         mLocationManager = locationManager;
         mGeocoder = new Geocoder(context);
         mConnectivityManager = connectivityManager;
+        mFeatureFlags = featureFlags;
         initialize();
     }
 
     /** @return the singleton instance of the {@link TelephonyCountryDetector} */
-    public static synchronized TelephonyCountryDetector getInstance(@NonNull Context context) {
+    public static synchronized TelephonyCountryDetector getInstance(@NonNull Context context,
+            FeatureFlags featureFlags) {
         if (sInstance == null) {
             HandlerThread handlerThread = new HandlerThread("TelephonyCountryDetector");
             handlerThread.start();
             sInstance = new TelephonyCountryDetector(handlerThread.getLooper(), context,
                     context.getSystemService(LocationManager.class),
-                    context.getSystemService(ConnectivityManager.class));
+                    context.getSystemService(ConnectivityManager.class),
+                    featureFlags);
         }
         return sInstance;
     }
@@ -286,6 +298,8 @@
                 handleNetworkCountryCodeChangedEvent((NetworkCountryCodeInfo) msg.obj);
                 break;
             case EVENT_WIFI_CONNECTIVITY_STATE_CHANGED:
+                handleEventWifiConnectivityStateChanged((boolean) msg.obj);
+                break;
             case EVENT_LOCATION_UPDATE_REQUEST_QUOTA_RESET:
                 evaluateRequestingLocationUpdates();
                 break;
@@ -468,6 +482,27 @@
             }
         }
         evaluateRequestingLocationUpdates();
+        if (mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("mCountryCodeChangedRegistrants.notifyRegistrants()");
+            mCountryCodeChangedRegistrants.notifyRegistrants();
+        } else {
+            logd("mCountryCodeChangedRegistrants.notifyRegistrants() is not called");
+        }
+    }
+
+    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) {
@@ -501,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);
@@ -533,13 +566,41 @@
         }
     }
 
-    private 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);
+    /**
+     * Check whether Wi-Fi network is connected or not.
+     * @return {@code true} is Wi-Fi is connected, and internet is available, {@code false}
+     * otherwise.
+     */
+    public boolean isWifiNetworkConnected() {
+        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;
+    }
+
+    /**
+     * Register a callback to receive Wi-Fi connectivity state changes.
+     * @param h Handler for notification message
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForWifiConnectivityStateChanged(@NonNull Handler h, int what,
+            @Nullable Object obj) {
+        mWifiConnectivityStateChangedRegistrantList.add(h, what, obj);
+    }
+
+    /**
+     * Unregisters for Wi-Fi connectivity state changes.
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForWifiConnectivityStateChanged(@NonNull Handler h) {
+        mWifiConnectivityStateChangedRegistrantList.remove(h);
     }
 
     /**
@@ -558,6 +619,26 @@
                 || SystemProperties.getBoolean(BOOT_ALLOW_MOCK_MODEM_PROPERTY, false));
     }
 
+    /**
+     * Register a callback for country code changed events
+     *
+     * @param h    Handler to notify
+     * @param what msg.what when the message is delivered
+     * @param obj  AsyncResult.userObj when the message is delivered
+     */
+    public void registerForCountryCodeChanged(Handler h, int what, Object obj) {
+        mCountryCodeChangedRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregister a callback for country code changed events
+     *
+     * @param h Handler to notifyf
+     */
+    public void unregisterForCountryCodeChanged(Handler h) {
+        mCountryCodeChangedRegistrants.remove(h);
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java b/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
index e374811..64ecdc9 100644
--- a/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
+++ b/src/java/com/android/internal/telephony/VisualVoicemailSmsFilter.java
@@ -35,6 +35,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.VisualVoicemailSmsParser.WrappedMessageData;
+import com.android.internal.telephony.flags.Flags;
 
 import java.nio.ByteBuffer;
 import java.nio.charset.CharacterCodingException;
@@ -286,7 +287,11 @@
         intent.putExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS, builder.build());
         intent.putExtra(VoicemailContract.EXTRA_TARGET_PACKAGE, filterSettings.packageName);
         intent.setPackage(TELEPHONY_SERVICE_PACKAGE);
-        context.sendBroadcast(intent);
+        if (Flags.hsumBroadcast()) {
+            context.sendBroadcastAsUser(intent, UserHandle.ALL);
+        } else {
+            context.sendBroadcast(intent);
+        }
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/WapPushOverSms.java b/src/java/com/android/internal/telephony/WapPushOverSms.java
index 7669411..9bede69 100644
--- a/src/java/com/android/internal/telephony/WapPushOverSms.java
+++ b/src/java/com/android/internal/telephony/WapPushOverSms.java
@@ -23,6 +23,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
@@ -45,6 +46,7 @@
 import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
@@ -67,8 +69,14 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private final Context mContext;
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    private UserManager mUserManager;
+
     PowerWhitelistManager mPowerWhitelistManager;
 
+    protected final @NonNull FeatureFlags mFeatureFlags;
+
     private String mWapPushManagerPackage;
 
     /** Assigned from ServiceConnection callback on main threaad. */
@@ -128,11 +136,11 @@
         if (DBG) Rlog.v(TAG, "wappush manager disconnected.");
     }
 
-    public WapPushOverSms(Context context) {
+    public WapPushOverSms(Context context, FeatureFlags featureFlags) {
+        mFeatureFlags = featureFlags;
         mContext = context;
         mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class);
-
-        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        mUserManager = mContext.getSystemService(UserManager.class);
 
         bindWapPushManagerService(mContext);
     }
@@ -325,6 +333,7 @@
      *         {@link Activity#RESULT_OK} if the message has been broadcast
      *         to applications
      */
+    @SuppressLint("MissingPermission")
     public int dispatchWapPdu(byte[] pdu, InboundSmsHandler.SmsBroadcastReceiver receiver,
             InboundSmsHandler handler, String address, int subId, long messageId) {
         DecodedResult result = decodeWapPdu(pdu, handler);
@@ -422,7 +431,11 @@
         }
 
         if (userHandle == null) {
-            userHandle = UserHandle.SYSTEM;
+            if (mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser()) {
+                userHandle = mUserManager.getMainUser();
+            } else {
+                userHandle = UserHandle.SYSTEM;
+            }
         }
         handler.dispatchIntent(intent, getPermissionForType(result.mimeType),
                 getAppOpsStringPermissionForIntent(result.mimeType), options, receiver,
diff --git a/src/java/com/android/internal/telephony/cat/CatService.java b/src/java/com/android/internal/telephony/cat/CatService.java
index 4da1622..5fdb8ce 100644
--- a/src/java/com/android/internal/telephony/cat/CatService.java
+++ b/src/java/com/android/internal/telephony/cat/CatService.java
@@ -40,6 +40,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -712,7 +713,12 @@
         intent.putExtra("SLOT_ID", mSlotId);
         intent.setComponent(AppInterface.getDefaultSTKApplication());
         CatLog.d(this, "Sending CmdMsg: " + cmdMsg+ " on slotid:" + mSlotId);
-        mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
+
+        if (sFlags.hsumBroadcast()) {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL, AppInterface.STK_PERMISSION);
+        } else {
+            mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
+        }
     }
 
     /**
@@ -726,7 +732,11 @@
         Intent intent = new Intent(AppInterface.CAT_SESSION_END_ACTION);
         intent.putExtra("SLOT_ID", mSlotId);
         intent.setComponent(AppInterface.getDefaultSTKApplication());
-        mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
+        if (sFlags.hsumBroadcast()) {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL, AppInterface.STK_PERMISSION);
+        } else {
+            mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
+        }
     }
 
 
@@ -1090,7 +1100,11 @@
         intent.putExtra("SLOT_ID", mSlotId);
         CatLog.d(this, "Sending Card Status: "
                 + cardState + " " + "cardPresent: " + cardPresent +  "SLOT_ID: " +  mSlotId);
-        mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
+        if (sFlags.hsumBroadcast()) {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL, AppInterface.STK_PERMISSION);
+        } else {
+            mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
+        }
     }
 
     private void broadcastAlphaMessage(String alphaString) {
@@ -1100,7 +1114,11 @@
         intent.putExtra(AppInterface.ALPHA_STRING, alphaString);
         intent.putExtra("SLOT_ID", mSlotId);
         intent.setComponent(AppInterface.getDefaultSTKApplication());
-        mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
+        if (sFlags.hsumBroadcast()) {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL, AppInterface.STK_PERMISSION);
+        } else {
+            mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
+        }
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
index 784c974..9a85329 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
@@ -41,6 +41,7 @@
 import com.android.internal.telephony.cdma.sms.BearerData;
 import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.util.HexDump;
 
 import java.io.ByteArrayOutputStream;
@@ -78,8 +79,9 @@
      * Create a new inbound SMS handler for CDMA.
      */
     private CdmaInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor,
-            Phone phone, CdmaSMSDispatcher smsDispatcher, Looper looper) {
-        super("CdmaInboundSmsHandler", context, storageMonitor, phone, looper);
+            Phone phone, CdmaSMSDispatcher smsDispatcher, Looper looper,
+            FeatureFlags featureFlags) {
+        super("CdmaInboundSmsHandler", context, storageMonitor, phone, looper, featureFlags);
         mSmsDispatcher = smsDispatcher;
         phone.mCi.setOnNewCdmaSms(getHandler(), EVENT_NEW_SMS, null);
 
@@ -171,9 +173,9 @@
      */
     public static CdmaInboundSmsHandler makeInboundSmsHandler(Context context,
             SmsStorageMonitor storageMonitor, Phone phone, CdmaSMSDispatcher smsDispatcher,
-            Looper looper) {
+            Looper looper, FeatureFlags featureFlags) {
         CdmaInboundSmsHandler handler = new CdmaInboundSmsHandler(context, storageMonitor,
-                phone, smsDispatcher, looper);
+                phone, smsDispatcher, looper, featureFlags);
         handler.start();
         return handler;
     }
diff --git a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameData.java b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameData.java
index 59745dc..597bad3 100644
--- a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameData.java
+++ b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameData.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.Rlog;
 
 import java.util.Objects;
 
@@ -25,6 +26,9 @@
  * A container of carrier display name.
  */
 public class CarrierDisplayNameData implements Parcelable {
+
+    private static final String LOG_TAG = "CarrierDisplayNameData";
+
     /** Service provider name. */
     private final String mSpn;
 
@@ -40,8 +44,18 @@
     /** {@code True} if display PLMN network name is required. */
     private final boolean mShowPlmn;
 
-    private CarrierDisplayNameData(String spn, String dataSpn, boolean showSpn, String plmn,
-            boolean showPlmn) {
+    private CarrierDisplayNameData(
+            String spn, String dataSpn, boolean showSpn, String plmn, boolean showPlmn) {
+        final String logString = "Data SPN must be provided if SPN is provided";
+        if (spn != null && dataSpn == null) {
+            Rlog.e(LOG_TAG, logString);
+            if (android.os.Build.isDebuggable()) {
+                throw new IllegalArgumentException(logString);
+            } else {
+                dataSpn = spn;
+            }
+        }
+
         this.mSpn = spn;
         this.mDataSpn = dataSpn;
         this.mShowSpn = showSpn;
diff --git a/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java b/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
index 4b5eebc..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;
     }
@@ -340,6 +340,7 @@
 
         return new CarrierDisplayNameData.Builder()
                 .setSpn(spn)
+                .setDataSpn(spn)
                 .setShowSpn(showSpn)
                 .setPlmn(plmn)
                 .setShowPlmn(showPlmn)
@@ -477,7 +478,8 @@
         CarrierDisplayNameData data = getCarrierDisplayNameFromEf();
         if (DBG) Rlog.d(TAG, "CarrierName from EF: " + data);
         if ((mPhone.getImsPhone() != null) && (mPhone.getImsPhone().getImsRegistrationTech()
-                == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM)) {
+                == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM
+                && mPhone.isImsRegistered())) {
             data = getCarrierDisplayNameFromCrossSimCallingOverride(data);
             if (DBG) {
                 Rlog.d(TAG, "CarrierName override by Cross-SIM Calling " + data);
diff --git a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
index bc684af..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
@@ -414,15 +414,19 @@
 
         // bindQualifiedNetworksService posts real work to handler thread. So here we can
         // let the callback execute in binder thread to avoid post twice.
-        mCarrierConfigManager.registerCarrierConfigChangeListener(Runnable::run,
-                (slotIndex, subId, carrierId, specificCarrierId) -> {
-                    if (slotIndex != mPhone.getPhoneId()) return;
-                    // We should wait for carrier config changed event because the target binding
-                    // package name can come from the carrier config. Note that we still get this
-                    // event even when SIM is absent.
-                    if (DBG) log("Carrier config changed. Try to bind qualified network service.");
-                    bindQualifiedNetworksService();
-                });
+        if (mCarrierConfigManager != null) {
+            mCarrierConfigManager.registerCarrierConfigChangeListener(Runnable::run,
+                    (slotIndex, subId, carrierId, specificCarrierId) -> {
+                        if (slotIndex != mPhone.getPhoneId()) return;
+                        // We should wait for carrier config changed event because the target
+                        // binding package name can come from the carrier config. Note that we still
+                        // get this event even when SIM is absent.
+                        if (DBG) {
+                            log("Carrier config changed. Try to bind qualified network service.");
+                        }
+                        bindQualifiedNetworksService();
+                    });
+        }
         bindQualifiedNetworksService();
 
         // Using post to delay the registering because data retry manager and data config
diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java
index 9f4fa11..89b0fec 100644
--- a/src/java/com/android/internal/telephony/data/DataConfigManager.java
+++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.StringDef;
 import android.content.res.Resources;
 import android.net.LinkProperties;
@@ -272,7 +273,7 @@
     private final String mLogTag;
     @NonNull
     private final FeatureFlags mFeatureFlags;
-    @NonNull
+    @Nullable
     private final CarrierConfigManager mCarrierConfigManager;
     @NonNull
     private PersistableBundle mCarrierConfig = null;
@@ -355,12 +356,14 @@
 
         mCarrierConfigManager = mPhone.getContext().getSystemService(CarrierConfigManager.class);
         // Callback send msg to handler thread, so callback itself can be executed in binder thread.
-        mCarrierConfigManager.registerCarrierConfigChangeListener(Runnable::run,
-                (slotIndex, subId, carrierId, specificCarrierId) -> {
-                    if (slotIndex == mPhone.getPhoneId()) {
-                        sendEmptyMessage(EVENT_CARRIER_CONFIG_CHANGED);
-                    }
-                });
+        if (mCarrierConfigManager != null) {
+            mCarrierConfigManager.registerCarrierConfigChangeListener(Runnable::run,
+                    (slotIndex, subId, carrierId, specificCarrierId) -> {
+                        if (slotIndex == mPhone.getPhoneId()) {
+                            sendEmptyMessage(EVENT_CARRIER_CONFIG_CHANGED);
+                        }
+                    });
+        }
 
         // Register for device config update
         DeviceConfig.addOnPropertiesChangedListener(
@@ -640,10 +643,8 @@
         // Consumer slices are the slices that are allowed to be accessed by regular application to
         // get better performance. They should be metered. This can be turned into configurations in
         // the future.
-        if (mFeatureFlags.meteredEmbbUrlcc()) {
-            meteredCapabilities.add(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
-            meteredCapabilities.add(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY);
-        }
+        meteredCapabilities.add(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH);
+        meteredCapabilities.add(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY);
 
         return Collections.unmodifiableSet(meteredCapabilities);
     }
@@ -758,7 +759,6 @@
      */
     public boolean allowBringUpNetworkInNonVops(@NetworkRegistrationInfo.RegistrationState
             int regState) {
-        if (!mFeatureFlags.allowMmtelInNonVops()) return false;
         int networkType = -1;
         if (regState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
             networkType = CarrierConfigManager.Ims.NETWORK_TYPE_HOME;
@@ -1503,14 +1503,6 @@
     }
 
     /**
-     * {@code True} enables mms to be attempted on iwlan if possible, even if existing cellular
-     *  networks already supports iwlan.
-     */
-    public boolean isForceIwlanMmsFeatureEnabled() {
-        return mResources.getBoolean(com.android.internal.R.bool.force_iwlan_mms_feature_enabled);
-    }
-
-    /**
      * Log debug messages.
      * @param s debug messages
      */
diff --git a/src/java/com/android/internal/telephony/data/DataEvaluation.java b/src/java/com/android/internal/telephony/data/DataEvaluation.java
index f5eae91..40c0081 100644
--- a/src/java/com/android/internal/telephony/data/DataEvaluation.java
+++ b/src/java/com/android/internal/telephony/data/DataEvaluation.java
@@ -225,6 +225,8 @@
         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/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index 87c392e..738c60f 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -100,6 +100,7 @@
 import com.android.internal.telephony.data.DataRetryManager.DataRetryEntry;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthEstimatorCallback;
+import com.android.internal.telephony.data.PhoneSwitcher.PhoneSwitcherCallback;
 import com.android.internal.telephony.data.TelephonyNetworkAgent.TelephonyNetworkAgentCallback;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.DataCallSessionStats;
@@ -271,6 +272,9 @@
     /** Event for response to data network validation request. */
     private static final int EVENT_DATA_NETWORK_VALIDATION_RESPONSE = 29;
 
+    /** Event for preferred data subscription changed. */
+    private static final int EVENT_PREFERRED_DATA_SUBSCRIPTION_CHANGED = 30;
+
     /** Invalid context id. */
     private static final int INVALID_CID = -1;
 
@@ -460,7 +464,10 @@
             // Dynamically add and remove MMTEL capability when network transition between VoPS
             // and non-VoPS network if the request is not MMTEL. For MMTEL, we retain the capability
             // to prevent immediate tear down.
-            NetworkCapabilities.NET_CAPABILITY_MMTEL
+            NetworkCapabilities.NET_CAPABILITY_MMTEL,
+            // Dynamically add and remove MMS capability depending on QNS's preference if there is
+            // a transport specific APN alternative.
+            NetworkCapabilities.NET_CAPABILITY_MMS
     );
 
     /** The parent state. Any messages not handled by the child state fallback to this. */
@@ -588,6 +595,10 @@
     @NonNull
     private final DataNetworkController mDataNetworkController;
 
+    /** Phone switcher which is responsible to determine which phone to route network request. */
+    @NonNull
+    private final PhoneSwitcher mPhoneSwitcher;
+
     /** Data network controller callback. */
     @NonNull
     private final DataNetworkController.DataNetworkControllerCallback
@@ -673,6 +684,13 @@
     /** Whether the current data network is congested. */
     private boolean mCongested = false;
 
+    /**
+     * Whether the current data network is on preferred data modem.
+     *
+     * @see PhoneSwitcher#getPreferredDataPhoneId()
+     */
+    private boolean mOnPreferredDataPhone;
+
     /** The network requests associated with this data network */
     @NonNull
     private final NetworkRequestList mAttachedNetworkRequestList =
@@ -812,6 +830,12 @@
     private PreciseDataConnectionState mPreciseDataConnectionState;
 
     /**
+     * Callback to listen event from {@link PhoneSwitcher}.
+     */
+    @NonNull
+    private PhoneSwitcherCallback mPhoneSwitcherCallback;
+
+    /**
      * The network bandwidth.
      */
     public static class NetworkBandwidth {
@@ -1024,6 +1048,8 @@
         mAccessNetworksManager = phone.getAccessNetworksManager();
         mVcnManager = mPhone.getContext().getSystemService(VcnManager.class);
         mDataNetworkController = phone.getDataNetworkController();
+        mPhoneSwitcher = PhoneSwitcher.getInstance();
+        mOnPreferredDataPhone = phone.getPhoneId() == mPhoneSwitcher.getPreferredDataPhoneId();
         mDataNetworkControllerCallback = new DataNetworkController.DataNetworkControllerCallback(
                 getHandler()::post) {
             @Override
@@ -1153,14 +1179,25 @@
             configBuilder.setNat64DetectionEnabled(false);
         }
 
-        final NetworkFactory factory = PhoneFactory.getNetworkFactory(
-                mPhone.getPhoneId());
-        final NetworkProvider provider = (null == factory) ? null : factory.getProvider();
+        NetworkProvider provider;
+        if (mFlags.supportNetworkProvider()) {
+            provider = PhoneFactory.getNetworkProvider();
+        } else {
+            final NetworkFactory factory = PhoneFactory.getNetworkFactory(
+                    mPhone.getPhoneId());
+            provider = (null == factory) ? null : factory.getProvider();
+        }
 
-        mNetworkScore = new NetworkScore.Builder()
-               .setKeepConnectedReason(isHandoverInProgress()
+        NetworkScore.Builder builder = new NetworkScore.Builder()
+                .setKeepConnectedReason(isHandoverInProgress()
                         ? NetworkScore.KEEP_CONNECTED_FOR_HANDOVER
-                        : NetworkScore.KEEP_CONNECTED_NONE).build();
+                        : NetworkScore.KEEP_CONNECTED_NONE);
+        if (mFlags.supportNetworkProvider()) {
+            builder.setTransportPrimary(mOnPreferredDataPhone);
+        }
+        mNetworkScore = builder.build();
+        logl("mNetworkScore: isPrimary=" + mNetworkScore.isTransportPrimary()
+                + ", keepConnectedReason=" + mNetworkScore.getKeepConnectedReason());
 
         return new TelephonyNetworkAgent(mPhone, getHandler().getLooper(), this,
                 mNetworkScore, configBuilder.build(), provider,
@@ -1222,6 +1259,16 @@
             mDataNetworkController.getDataSettingsManager()
                     .registerCallback(mDataSettingsManagerCallback);
 
+            if (mFlags.supportNetworkProvider()) {
+                mPhoneSwitcherCallback = new PhoneSwitcherCallback(Runnable::run) {
+                    @Override
+                    public void onPreferredDataPhoneIdChanged(int phoneId) {
+                        sendMessage(EVENT_PREFERRED_DATA_SUBSCRIPTION_CHANGED, phoneId, 0);
+                    }
+                };
+                mPhoneSwitcher.registerCallback(mPhoneSwitcherCallback);
+            }
+
             mPhone.getDisplayInfoController().registerForTelephonyDisplayInfoChanged(
                     getHandler(), EVENT_DISPLAY_INFO_CHANGED, null);
             mPhone.getServiceStateTracker().registerForServiceStateChanged(getHandler(),
@@ -1263,21 +1310,19 @@
                         getHandler(), EVENT_VOICE_CALL_ENDED, null);
             }
 
-            if (mFlags.forceIwlanMms() && mDataConfigManager.isForceIwlanMmsFeatureEnabled()) {
-                if (mDataProfile.canSatisfy(NetworkCapabilities.NET_CAPABILITY_MMS)) {
-                    mAccessNetworksManagerCallback = new AccessNetworksManagerCallback(
-                            getHandler()::post) {
-                        @Override
-                        public void onPreferredTransportChanged(
-                                @NetCapability int networkCapability, boolean forceReconnect) {
-                            if (networkCapability == NetworkCapabilities.NET_CAPABILITY_MMS) {
-                                log("MMS preference changed.");
-                                updateNetworkCapabilities();
-                            }
+            if (mDataProfile.canSatisfy(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+                mAccessNetworksManagerCallback = new AccessNetworksManagerCallback(
+                        getHandler()::post) {
+                    @Override
+                    public void onPreferredTransportChanged(
+                            @NetCapability int networkCapability, boolean forceReconnect) {
+                        if (networkCapability == NetworkCapabilities.NET_CAPABILITY_MMS) {
+                            log("MMS preference changed.");
+                            updateNetworkCapabilities();
                         }
-                    };
-                    mAccessNetworksManager.registerCallback(mAccessNetworksManagerCallback);
-                }
+                    }
+                };
+                mAccessNetworksManager.registerCallback(mAccessNetworksManagerCallback);
             }
 
             // Only add symmetric code here, for example, registering and unregistering.
@@ -1289,8 +1334,7 @@
         @Override
         public void exit() {
             logv("Unregistering all events.");
-            if (mFlags.forceIwlanMms() && mAccessNetworksManagerCallback != null
-                    && mDataConfigManager.isForceIwlanMmsFeatureEnabled()) {
+            if (mAccessNetworksManagerCallback != null) {
                 mAccessNetworksManager.unregisterCallback(mAccessNetworksManagerCallback);
             }
 
@@ -1316,6 +1360,9 @@
             mPhone.getServiceStateTracker().unregisterForServiceStateChanged(getHandler());
             mPhone.getDisplayInfoController().unregisterForTelephonyDisplayInfoChanged(
                     getHandler());
+            if (mFlags.supportNetworkProvider()) {
+                mPhoneSwitcher.unregisterCallback(mPhoneSwitcherCallback);
+            }
             mDataNetworkController.getDataSettingsManager()
                     .unregisterCallback(mDataSettingsManagerCallback);
             mRil.unregisterForPcoData(getHandler());
@@ -1349,13 +1396,13 @@
                 }
                 case EVENT_ATTACH_NETWORK_REQUEST: {
                     onAttachNetworkRequests((NetworkRequestList) msg.obj);
-                    updateNetworkScore(isHandoverInProgress());
+                    updateNetworkScore();
                     break;
                 }
                 case EVENT_DETACH_NETWORK_REQUEST: {
                     onDetachNetworkRequest((TelephonyNetworkRequest) msg.obj,
                             msg.arg1 != 0 /* shouldRetry */);
-                    updateNetworkScore(isHandoverInProgress());
+                    updateNetworkScore();
                     break;
                 }
                 case EVENT_DETACH_ALL_NETWORK_REQUESTS: {
@@ -1426,6 +1473,12 @@
                     // handle the resultCode in response for the request.
                     handleDataNetworkValidationRequestResultCode(msg.arg1 /* resultCode */);
                     break;
+                case EVENT_PREFERRED_DATA_SUBSCRIPTION_CHANGED:
+                    mOnPreferredDataPhone = mPhone.getPhoneId() == msg.arg1;
+                    logl("Preferred data phone id changed to " + msg.arg1
+                            + ", mOnPreferredDataPhone=" + mOnPreferredDataPhone);
+                    updateNetworkScore();
+                    break;
                 default:
                     loge("Unhandled event " + eventToString(msg.what));
                     break;
@@ -2353,14 +2406,7 @@
                     DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo();
                     // Check if the network is non-VoPS.
                     if (dsri != null && dsri.getVopsSupportInfo() != null
-                            && !dsri.getVopsSupportInfo().isVopsSupported()
-                            // Reflect the actual MMTEL if flag on.
-                            && (mFlags.allowMmtelInNonVops()
-                            // Deceive Connectivity service to satisfy an MMTEL request, this should
-                            // be useless because we reach here if no MMTEL request, then removing
-                            // MMTEL capability shouldn't have any impacts.
-                            || !mDataConfigManager.shouldKeepNetworkUpInNonVops(
-                                    nri.getNetworkRegistrationState()))) {
+                            && !dsri.getVopsSupportInfo().isVopsSupported()) {
                         builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL);
                     }
                     log("updateNetworkCapabilities: dsri=" + dsri);
@@ -2488,9 +2534,7 @@
         // Check if the feature force MMS on IWLAN is enabled. When the feature is enabled, MMS
         // will be attempted on IWLAN if possible, even if existing cellular networks already
         // supports IWLAN.
-        if (mFlags.forceIwlanMms() && builder.build()
-                .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
-                && mDataConfigManager.isForceIwlanMmsFeatureEnabled()) {
+        if (builder.build().hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
             // If QNS sets MMS preferred on IWLAN, and it is possible to setup an MMS network on
             // IWLAN, then we need to remove the MMS capability on the cellular network. This will
             // allow the new MMS network to be brought up on IWLAN when MMS network request arrives.
@@ -3313,6 +3357,12 @@
         return mLinkStatus;
     }
 
+    /**
+     * Update the network score and report to connectivity service if necessary.
+     */
+    private void updateNetworkScore() {
+        updateNetworkScore(isHandoverInProgress());
+    }
 
     /**
      * Update the network score and report to connectivity service if necessary.
@@ -3322,10 +3372,18 @@
     private void updateNetworkScore(boolean keepConnectedForHandover) {
         int connectedReason = keepConnectedForHandover
                 ? NetworkScore.KEEP_CONNECTED_FOR_HANDOVER : NetworkScore.KEEP_CONNECTED_NONE;
-        if (mNetworkScore.getKeepConnectedReason() != connectedReason) {
-            mNetworkScore = new NetworkScore.Builder()
-                    .setKeepConnectedReason(connectedReason).build();
+        if (mNetworkScore.getKeepConnectedReason() != connectedReason
+                || (mFlags.supportNetworkProvider()
+                && mNetworkScore.isTransportPrimary() != mOnPreferredDataPhone)) {
+            NetworkScore.Builder builder = new NetworkScore.Builder()
+                    .setKeepConnectedReason(connectedReason);
+            if (mFlags.supportNetworkProvider()) {
+                builder.setTransportPrimary(mOnPreferredDataPhone);
+            }
+            mNetworkScore = builder.build();
             mNetworkAgent.sendNetworkScore(mNetworkScore);
+            logl("updateNetworkScore: isPrimary=" + mNetworkScore.isTransportPrimary()
+                    + ", keepConnectedForHandover=" + keepConnectedForHandover);
         }
     }
 
@@ -3359,7 +3417,7 @@
     public int getApnTypeNetworkCapability() {
         if (!mAttachedNetworkRequestList.isEmpty()) {
             // The highest priority network request is always at the top of list.
-            return mAttachedNetworkRequestList.get(0).getApnTypeNetworkCapability();
+            return mAttachedNetworkRequestList.get(0).getHighestPriorityApnTypeNetworkCapability();
         } else {
             return Arrays.stream(getNetworkCapabilities().getCapabilities()).boxed()
                     .filter(cap -> DataUtils.networkCapabilityToApnType(cap)
@@ -4054,12 +4112,14 @@
         pw.println("Tag: " + name());
         pw.increaseIndent();
         pw.println("mSubId=" + mSubId);
+        pw.println("mOnPreferredDataPhone=" + mOnPreferredDataPhone);
         pw.println("mTransport=" + AccessNetworkConstants.transportTypeToString(mTransport));
         pw.println("mLastKnownDataNetworkType=" + TelephonyManager
                 .getNetworkTypeName(mLastKnownDataNetworkType));
         pw.println("WWAN cid=" + mCid.get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN));
         pw.println("WLAN cid=" + mCid.get(AccessNetworkConstants.TRANSPORT_TYPE_WLAN));
         pw.println("mNetworkScore=" + mNetworkScore);
+        pw.println("keepConnectedReason=" + mNetworkScore.getKeepConnectedReason());
         pw.println("mDataAllowedReason=" + mDataAllowedReason);
         pw.println("mPduSessionId=" + mPduSessionId);
         pw.println("mDataProfile=" + mDataProfile);
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 010e0fe..9e432e1 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -1299,6 +1299,13 @@
      * @param networkRequest The network request.
      */
     private void onAddNetworkRequest(@NonNull TelephonyNetworkRequest networkRequest) {
+        // TelephonyNetworkRequest at TelephonyNetworkProvider layer does not have config assigned
+        // (Because TelephonyNetworkProvider is a singleton across all SIMs. We are not able to
+        // retrieve the right carrier config for it.). So as soon as the request arrives
+        // DataNetworkController, we need to update the config in the request so it can update
+        // some of its config-dependent properties like request priority.
+        networkRequest.updateDataConfig(mDataConfigManager);
+
         // To detect IMS back-to-back release-request anomaly event
         if (mLastImsOperationIsRelease) {
             mLastImsOperationIsRelease = false;
@@ -1501,11 +1508,9 @@
                         .build(), mPhone, mFeatureFlags);
         // If we don't skip checking existing network, then we should check If one of the
         // existing networks can satisfy the internet request, then internet is allowed.
-        if ((!mFeatureFlags.ignoreExistingNetworksForInternetAllowedChecking()
-                || !ignoreExistingNetworks)
-                && mDataNetworkList.stream().anyMatch(
-                        dataNetwork -> internetRequest.canBeSatisfiedBy(
-                                dataNetwork.getNetworkCapabilities()))) {
+        if (!ignoreExistingNetworks && mDataNetworkList.stream().anyMatch(
+                dataNetwork -> internetRequest.canBeSatisfiedBy(
+                        dataNetwork.getNetworkCapabilities()))) {
             return new DataEvaluation(DataEvaluationReason.EXTERNAL_QUERY);
         }
 
@@ -1579,7 +1584,7 @@
             @NonNull TelephonyNetworkRequest networkRequest, DataEvaluationReason reason) {
         DataEvaluation evaluation = new DataEvaluation(reason);
         int transport = mAccessNetworksManager.getPreferredTransportByNetworkCapability(
-                networkRequest.getApnTypeNetworkCapability());
+                networkRequest.getHighestPriorityApnTypeNetworkCapability());
 
         // Check if the request can be satisfied by cellular network or satellite network.
         if (mFeatureFlags.satelliteInternet()
@@ -1711,7 +1716,7 @@
 
         if (mDataSettingsManager.isDataInitialized()) {
             if (!mDataSettingsManager.isDataEnabled(DataUtils.networkCapabilityToApnType(
-                    networkRequest.getApnTypeNetworkCapability()))) {
+                    networkRequest.getHighestPriorityApnTypeNetworkCapability()))) {
                 evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_DISABLED);
             }
         } else {
@@ -1746,7 +1751,9 @@
                 // Check if request is unmetered (WiFi or unmetered APN).
                 evaluation.addDataAllowedReason(DataAllowedReason.UNMETERED_USAGE);
             } else if (transport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
-                if (!networkRequest.isMeteredRequest()) {
+                boolean isMeteredRequest = mDataConfigManager.isAnyMeteredCapability(
+                        networkRequest.getCapabilities(), mServiceState.getDataRoaming());
+                if (!isMeteredRequest) {
                     evaluation.addDataAllowedReason(DataAllowedReason.UNMETERED_USAGE);
                 }
             }
@@ -1985,7 +1992,7 @@
             if (mAllNetworkRequestList.stream()
                     .filter(request -> dataNetwork.getTransport()
                             == mAccessNetworksManager.getPreferredTransportByNetworkCapability(
-                                    request.getApnTypeNetworkCapability()))
+                                    request.getHighestPriorityApnTypeNetworkCapability()))
                     .filter(request
                             -> !hasCapabilityExemptsFromSinglePdnRule(request.getCapabilities()))
                     .anyMatch(request -> request.getPriority() > dataNetwork.getPriority())) {
@@ -2507,6 +2514,30 @@
     }
 
     private void onRemoveNetworkRequest(@NonNull TelephonyNetworkRequest request) {
+        if (mFeatureFlags.supportNetworkProvider()) {
+            if (!mAllNetworkRequestList.remove(request)) {
+                loge("onRemoveNetworkRequest: Network request does not exist. " + request);
+                return;
+            }
+
+            if (request.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+                mImsThrottleCounter.addOccurrence();
+                mLastReleasedImsRequestCapabilities = request.getCapabilities();
+                mLastImsOperationIsRelease = true;
+            }
+
+            if (request.getAttachedNetwork() != null) {
+                request.getAttachedNetwork().detachNetworkRequest(
+                        request, false /* shouldRetry */);
+            }
+
+            request.setState(TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED);
+            request.setEvaluation(null);
+
+            log("onRemoveNetworkRequest: Removed " + request);
+            return;
+        }
+
         // The request generated from telephony network factory does not contain the information
         // the original request has, for example, attached data network. We need to find the
         // original one.
@@ -2527,7 +2558,7 @@
 
         if (networkRequest.getAttachedNetwork() != null) {
             networkRequest.getAttachedNetwork().detachNetworkRequest(
-                        networkRequest, false /* shouldRetry */);
+                    networkRequest, false /* shouldRetry */);
         }
         log("onRemoveNetworkRequest: Removed " + networkRequest);
     }
@@ -2869,7 +2900,7 @@
         }
 
         int transport = mAccessNetworksManager.getPreferredTransportByNetworkCapability(
-                networkRequestList.get(0).getApnTypeNetworkCapability());
+                networkRequestList.get(0).getHighestPriorityApnTypeNetworkCapability());
         logl("Creating data network on "
                 + AccessNetworkConstants.transportTypeToString(transport) + " with " + dataProfile
                 + ", and attaching " + networkRequestList.size() + " network requests to it.");
@@ -3141,7 +3172,8 @@
         log("onDataNetworkSetupRetry: Request list:" + requestList);
         TelephonyNetworkRequest telephonyNetworkRequest = requestList.get(0);
 
-        int networkCapability = telephonyNetworkRequest.getApnTypeNetworkCapability();
+        int networkCapability = telephonyNetworkRequest
+                .getHighestPriorityApnTypeNetworkCapability();
         int preferredTransport = mAccessNetworksManager.getPreferredTransportByNetworkCapability(
                 networkCapability);
         if (preferredTransport != dataSetupRetryEntry.transport) {
@@ -3163,7 +3195,7 @@
                 telephonyNetworkRequest, DataEvaluationReason.DATA_RETRY);
         if (!evaluation.containsDisallowedReasons()) {
             DataProfile dataProfile = dataSetupRetryEntry.dataProfile;
-            if (dataProfile == null) {
+            if (dataProfile == null || !mDataProfileManager.isDataProfileCompatible(dataProfile)) {
                 dataProfile = evaluation.getCandidateDataProfile();
             }
             if (dataProfile != null) {
@@ -3511,15 +3543,6 @@
     }
 
     /**
-     * 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.)
@@ -3527,13 +3550,22 @@
     private void onSimStateChanged(@SimState int simState) {
         log("onSimStateChanged: state=" + TelephonyManager.simStateToString(simState));
         if (mSimState != simState) {
-            mSimState = simState;
             if (simState == TelephonyManager.SIM_STATE_ABSENT) {
-                onSimAbsent();
+                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));
+                }
             } 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)));
         }
@@ -3609,8 +3641,7 @@
             dataNetwork.startHandover(targetTransport, dataHandoverRetryEntry);
         } else if (dataNetwork.shouldDelayImsTearDownDueToInCall()
                 && (dataEvaluation.containsOnly(DataDisallowedReason.NOT_IN_SERVICE)
-                || mFeatureFlags.relaxHoTeardown() && dataEvaluation.isSubsetOf(
-                        DataDisallowedReason.NOT_IN_SERVICE,
+                || dataEvaluation.isSubsetOf(DataDisallowedReason.NOT_IN_SERVICE,
                         DataDisallowedReason.NOT_ALLOWED_BY_POLICY))) {
             // We try our best to preserve the voice call by retrying later
             if (dataHandoverRetryEntry != null) {
diff --git a/src/java/com/android/internal/telephony/data/DataProfileManager.java b/src/java/com/android/internal/telephony/data/DataProfileManager.java
index c50d50a..28fe935 100644
--- a/src/java/com/android/internal/telephony/data/DataProfileManager.java
+++ b/src/java/com/android/internal/telephony/data/DataProfileManager.java
@@ -432,35 +432,18 @@
      */
     private void onInternetDataNetworkConnected(@NonNull Set<DataNetwork> internetNetworks) {
         DataProfile defaultProfile = null;
-        if (mFeatureFlags.refinePreferredDataProfileSelection()) {
-            // Most of the cases there should be only one.
-            // but in case there are multiple, find the default internet network, and choose the
-            // one which has longest life cycle.
-            defaultProfile = internetNetworks.stream()
-                    .filter(network -> mPreferredDataProfile == null
-                            // Find the one most resembles the current preferred profile,
-                            // avoiding e.g. DUN default network.
-                            || canPreferredDataProfileSatisfy(
-                            network.getAttachedNetworkRequestList()))
-                    .map(DataNetwork::getDataProfile)
-                    .min(Comparator.comparingLong(DataProfile::getLastSetupTimestamp))
-                    .orElse(null);
-        } else {
-            if (internetNetworks.size() == 1) {
-                // Most of the cases there should be only one.
-                defaultProfile = internetNetworks.stream().findFirst().get().getDataProfile();
-            } else if (internetNetworks.size() > 1) {
-                // but in case there are multiple, find the default internet network, and choose the
-                // one which has longest life cycle.
-                defaultProfile = internetNetworks.stream()
-                        .filter(network -> mPreferredDataProfile == null
-                                || canPreferredDataProfileSatisfy(
-                                network.getAttachedNetworkRequestList()))
-                        .map(DataNetwork::getDataProfile)
-                        .min(Comparator.comparingLong(DataProfile::getLastSetupTimestamp))
-                        .orElse(null);
-            }
-        }
+        // Most of the cases there should be only one.
+        // but in case there are multiple, find the default internet network, and choose the
+        // one which has longest life cycle.
+        defaultProfile = internetNetworks.stream()
+                .filter(network -> mPreferredDataProfile == null
+                        // Find the one most resembles the current preferred profile,
+                        // avoiding e.g. DUN default network.
+                        || canPreferredDataProfileSatisfy(
+                        network.getAttachedNetworkRequestList()))
+                .map(DataNetwork::getDataProfile)
+                .min(Comparator.comparingLong(DataProfile::getLastSetupTimestamp))
+                .orElse(null);
 
         // Update a working internet data profile by subid as a future candidate for preferred
         // data profile after APNs are reset to default
diff --git a/src/java/com/android/internal/telephony/data/DataRetryManager.java b/src/java/com/android/internal/telephony/data/DataRetryManager.java
index d35e9e8..318c4b5 100644
--- a/src/java/com/android/internal/telephony/data/DataRetryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataRetryManager.java
@@ -22,11 +22,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlarmManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.net.NetworkCapabilities;
 import android.os.AsyncResult;
 import android.os.Handler;
@@ -79,11 +74,6 @@
 public class DataRetryManager extends Handler {
     private static final boolean VDBG = false;
 
-    /** Intent of Alarm Manager for long retry timer. */
-    private static final String ACTION_RETRY = "com.android.internal.telephony.data.ACTION_RETRY";
-    /** The extra key for the hashcode of the retry entry for Alarm Manager. */
-    private static final String ACTION_RETRY_EXTRA_HASHCODE = "extra_retry_hashcode";
-
     /** Event for data setup retry. */
     private static final int EVENT_DATA_SETUP_RETRY = 3;
 
@@ -1078,22 +1068,6 @@
         mRil.registerForOn(this, EVENT_RADIO_ON, null);
         mRil.registerForModemReset(this, EVENT_MODEM_RESET, null);
 
-        if (!mFlags.useAlarmCallback()) {
-            // Register intent of alarm manager for long retry timer
-            IntentFilter intentFilter = new IntentFilter();
-            intentFilter.addAction(ACTION_RETRY);
-            mPhone.getContext().registerReceiver(new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    if (ACTION_RETRY.equals(intent.getAction())) {
-                        DataRetryManager.this.onAlarmIntentRetry(
-                                intent.getIntExtra(ACTION_RETRY_EXTRA_HASHCODE,
-                                        -1 /*Bad hashcode*/));
-                    }
-                }
-            }, intentFilter);
-        }
-
         if (mDataConfigManager.shouldResetDataThrottlingWhenTacChanges()) {
             mPhone.getServiceStateTracker().registerForAreaCodeChanged(this, EVENT_TAC_CHANGED,
                     null);
@@ -1277,7 +1251,8 @@
                 return;
             }
             for (NetworkRequestList networkRequestList : groupedNetworkRequestLists) {
-                int capability = networkRequestList.get(0).getApnTypeNetworkCapability();
+                int capability = networkRequestList.get(0)
+                        .getHighestPrioritySupportedNetworkCapability();
                 if (retryRule.canBeMatched(capability, cause)) {
                     // Check if there is already a similar network request retry scheduled.
                     if (isSimilarNetworkRequestRetryScheduled(
@@ -1492,7 +1467,8 @@
                                 mPhone.getCarrierId());
                         continue;
                     }
-                    if (entry.networkRequestList.get(0).getApnTypeNetworkCapability()
+                    if (entry.networkRequestList.get(0)
+                            .getHighestPrioritySupportedNetworkCapability()
                             == networkCapability
                             && entry.appliedDataRetryRule.equals(dataRetryRule)) {
                         if (entry.getState() == DataRetryEntry.RETRY_STATE_SUCCEEDED
@@ -1529,32 +1505,19 @@
                             ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY, dataRetryEntry),
                     dataRetryEntry.retryDelayMillis);
         } else {
-            if (mFlags.useAlarmCallback()) {
-                // No need to wake up the device, the retry can wait util next time the device wake
-                // up to save power.
-                mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME,
-                        dataRetryEntry.retryElapsedTime,
-                        "dataRetryHash-" + dataRetryEntry.hashCode() /*debug tag*/,
-                        Runnable::run,
-                        null /*worksource*/,
-                        () -> {
-                            logl("onAlarm retry " + dataRetryEntry);
-                            sendMessage(obtainMessage(dataRetryEntry instanceof DataSetupRetryEntry
-                                            ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY,
-                                    dataRetryEntry));
-                        });
-            } else {
-                Intent intent = new Intent(ACTION_RETRY);
-                intent.putExtra(ACTION_RETRY_EXTRA_HASHCODE, dataRetryEntry.hashCode());
-                // No need to wake up the device, the retry can wait util next time the device wake
-                // up  to save power.
-                mAlarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME,
-                        dataRetryEntry.retryElapsedTime,
-                        PendingIntent.getBroadcast(mPhone.getContext(),
-                                dataRetryEntry.hashCode()/*Unique identifier of the retry attempt*/,
-                                intent,
-                                PendingIntent.FLAG_IMMUTABLE));
-            }
+            // No need to wake up the device, the retry can wait util next time the device wake
+            // up to save power.
+            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME,
+                    dataRetryEntry.retryElapsedTime,
+                    "dataRetryHash-" + dataRetryEntry.hashCode() /*debug tag*/,
+                    Runnable::run,
+                    null /*worksource*/,
+                    () -> {
+                        logl("onAlarm retry " + dataRetryEntry);
+                        sendMessage(obtainMessage(dataRetryEntry instanceof DataSetupRetryEntry
+                                        ? EVENT_DATA_SETUP_RETRY : EVENT_DATA_HANDOVER_RETRY,
+                                dataRetryEntry));
+                    });
         }
     }
 
@@ -1599,8 +1562,7 @@
         // transport.
         mDataThrottlingEntries.removeIf(
                 throttlingEntry -> dataProfile.equals(throttlingEntry.dataProfile)
-                        && (!mFlags.unthrottleCheckTransport()
-                        || throttlingEntry.transport == transport));
+                        && (throttlingEntry.transport == transport));
 
         if (mDataThrottlingEntries.size() >= MAXIMUM_HISTORICAL_ENTRIES) {
             // If we don't see the anomaly report after U release, we should remove this check for
@@ -1649,7 +1611,7 @@
             // profile manager.
             Stream<DataThrottlingEntry> stream = mDataThrottlingEntries.stream();
             stream = stream.filter(entry -> entry.expirationTimeMillis > now
-                    && (!mFlags.unthrottleCheckTransport() || entry.transport == transport));
+                    && entry.transport == transport);
             if (dataProfile.getApnSetting() != null) {
                 stream = stream
                         .filter(entry -> entry.dataProfile.getApnSetting() != null)
@@ -1779,8 +1741,9 @@
                                 mPhone.getCarrierId());
                         continue;
                     }
-                    if (entry.networkRequestList.get(0).getApnTypeNetworkCapability()
-                            == networkRequest.getApnTypeNetworkCapability()
+                    if (entry.networkRequestList.get(0)
+                            .getHighestPrioritySupportedNetworkCapability()
+                            == networkRequest.getHighestPrioritySupportedNetworkCapability()
                             && entry.transport == transport) {
                         return true;
                     }
diff --git a/src/java/com/android/internal/telephony/data/DataSettingsManager.java b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
index fb112d9..0b9ac27 100644
--- a/src/java/com/android/internal/telephony/data/DataSettingsManager.java
+++ b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
@@ -326,7 +326,7 @@
                     @Override
                     public void onUserDataEnabledChanged(boolean enabled,
                             @NonNull String callingPackage) {
-                        log("phone" + phone.getPhoneId() + " onUserDataEnabledChanged "
+                        log("phone " + phone.getPhoneId() + " onUserDataEnabledChanged "
                                 + enabled + " by " + callingPackage
                                 + ", reevaluating mobile data policies");
                         DataSettingsManager.this.updateDataEnabledAndNotify(
@@ -335,6 +335,16 @@
                 });
             }
         }
+        SubscriptionManagerService.getInstance().registerCallback(
+                new SubscriptionManagerService.SubscriptionManagerServiceCallback(this::post) {
+                    @Override
+                    public void onDefaultDataSubscriptionChanged(int subId) {
+                        log((subId == mSubId ? "Became" : "Not")
+                                + " default data sub, reevaluating mobile data policies");
+                        DataSettingsManager.this.updateDataEnabledAndNotify(
+                                TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
+                    }
+                });
         updateDataEnabledAndNotify(TelephonyManager.DATA_ENABLED_REASON_UNKNOWN);
     }
 
diff --git a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
index b9b60a0..42e1a78 100644
--- a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
@@ -32,6 +32,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.ValidationStatus;
@@ -714,7 +715,12 @@
         // Put the bundled stats extras on the intent.
         intent.putExtra("EXTRA_DSRS_STATS_BUNDLE", bundle);
 
-        mPhone.getContext().sendBroadcast(intent, READ_PRIVILEGED_PHONE_STATE);
+        if (mFeatureFlags.hsumBroadcast()) {
+            mPhone.getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
+                    READ_PRIVILEGED_PHONE_STATE);
+        } else {
+            mPhone.getContext().sendBroadcast(intent, READ_PRIVILEGED_PHONE_STATE);
+        }
     }
 
     /** Recovery Action: RECOVERY_ACTION_GET_DATA_CALL_LIST */
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 27b4331..62d33d9 100644
--- a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
@@ -30,6 +30,7 @@
 
 import static java.util.Arrays.copyOf;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
@@ -63,6 +64,7 @@
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.Log;
 import android.util.SparseIntArray;
@@ -89,6 +91,7 @@
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.OnDemandDataSwitch;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.internal.telephony.subscription.SubscriptionManagerService.SubscriptionManagerServiceCallback;
 import com.android.internal.telephony.subscription.SubscriptionManagerService.WatchedInt;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.telephony.Rlog;
@@ -182,6 +185,27 @@
         }
     }
 
+    /**
+     * Callback from PhoneSwitcher
+     */
+    public static class PhoneSwitcherCallback extends DataCallback {
+        /**
+         * Constructor
+         *
+         * @param executor The executor of the callback.
+         */
+        public PhoneSwitcherCallback(@NonNull @CallbackExecutor Executor executor) {
+            super(executor);
+        }
+
+        /**
+         * Called when preferred data phone id changed.
+         *
+         * @param phoneId The phone id of the preferred data.
+         */
+        public void onPreferredDataPhoneIdChanged(int phoneId) {}
+    }
+
     @NonNull
     private final NetworkRequestList mNetworkRequestList = new NetworkRequestList();
     protected final RegistrantList mActivePhoneRegistrants;
@@ -260,6 +284,10 @@
 
     private ISetOpportunisticDataCallback mSetOpptSubCallback;
 
+    /** Phone switcher callbacks. */
+    @NonNull
+    private final Set<PhoneSwitcherCallback> mPhoneSwitcherCallbacks = new ArraySet<>();
+
     private static final int EVENT_PRIMARY_DATA_SUB_CHANGED       = 101;
     protected static final int EVENT_SUBSCRIPTION_CHANGED         = 102;
     private static final int EVENT_REQUEST_NETWORK                = 103;
@@ -467,6 +495,24 @@
         }
     }
 
+    /**
+     * Register the callback for receiving information from {@link PhoneSwitcher}.
+     *
+     * @param callback The callback.
+     */
+    public void registerCallback(@NonNull PhoneSwitcherCallback callback) {
+        mPhoneSwitcherCallbacks.add(callback);
+    }
+
+    /**
+     * Unregister the callback for receiving information from {@link PhoneSwitcher}.
+     *
+     * @param callback The callback.
+     */
+    public void unregisterCallback(@NonNull PhoneSwitcherCallback callback) {
+        mPhoneSwitcherCallbacks.remove(callback);
+    }
+
     private void evaluateIfImmediateDataSwitchIsNeeded(String evaluationReason, int switchReason) {
         if (onEvaluate(REQUESTS_UNCHANGED, evaluationReason)) {
             logDataSwitchEvent(mPreferredDataSubId.get(),
@@ -587,54 +633,54 @@
         };
         mAutoDataSwitchController = new AutoDataSwitchController(context, looper, this,
                 mFlags, mAutoDataSwitchCallback);
-
-        mContext.registerReceiver(mDefaultDataChangedReceiver,
-                new IntentFilter(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED));
+        if (!mFlags.ddsCallback()) {
+            mContext.registerReceiver(mDefaultDataChangedReceiver,
+                    new IntentFilter(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED));
+        } else {
+            mSubscriptionManagerService.registerCallback(new SubscriptionManagerServiceCallback(
+                    this::post) {
+                @Override
+                public void onDefaultDataSubscriptionChanged(int subId) {
+                    evaluateIfImmediateDataSwitchIsNeeded("default data sub changed to " + subId,
+                            DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL);
+                }
+            });
+        }
 
         PhoneConfigurationManager.registerForMultiSimConfigChange(
                 this, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
 
         mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
 
-        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_IA)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_RCS)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_XCAP)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_MCX)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
-                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_1)
-                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_2)
-                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_3)
-                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_4)
-                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_5)
-                .setNetworkSpecifier(new MatchAllNetworkSpecifier());
+        if (!mFlags.supportNetworkProvider()) {
+            final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
+                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_IA)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)
+                    .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                    .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_1)
+                    .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_2)
+                    .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_3)
+                    .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_4)
+                    .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_5)
+                    .setNetworkSpecifier(new MatchAllNetworkSpecifier());
+            TelephonyNetworkRequest.getAllSupportedNetworkCapabilities()
+                    .forEach(builder::addCapability);
 
-        if (mFlags.satelliteInternet()) {
-            // TODO: b/328622096 remove the try/catch
-            try {
-                builder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
-            } catch (IllegalArgumentException exception) {
-                loge("TRANSPORT_SATELLITE is not supported.");
+            if (mFlags.satelliteInternet()) {
+                // TODO: b/328622096 remove the try/catch
+                try {
+                    builder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+                } catch (IllegalArgumentException exception) {
+                    loge("TRANSPORT_SATELLITE is not supported.");
+                }
             }
-        }
 
-        NetworkFactory networkFactory = new PhoneSwitcherNetworkRequestListener(looper, context,
-                builder.build(), this);
-        // we want to see all requests
-        networkFactory.registerIgnoringScore();
+            NetworkFactory networkFactory = new PhoneSwitcherNetworkRequestListener(looper, context,
+                    builder.build(), this);
+            // we want to see all requests
+            networkFactory.registerIgnoringScore();
+        }
 
         updateHalCommandToUse();
 
@@ -1054,11 +1100,12 @@
         return false;
     }
 
+    // TODO: Remove after removing TelephonyNetworkFactory
     private static class PhoneSwitcherNetworkRequestListener extends NetworkFactory {
         private final PhoneSwitcher mPhoneSwitcher;
         public PhoneSwitcherNetworkRequestListener (Looper l, Context c,
                 NetworkCapabilities nc, PhoneSwitcher ps) {
-            super(l, c, "PhoneSwitcherNetworkRequstListener", nc);
+            super(l, c, "PhoneSwitcherNetworkRequestListener", nc);
             mPhoneSwitcher = ps;
         }
 
@@ -1079,7 +1126,13 @@
         }
     }
 
-    private void onRequestNetwork(NetworkRequest networkRequest) {
+    /**
+     * Called when receiving a network request.
+     *
+     * @param networkRequest The network request.
+     */
+    // TODO: Transform to TelephonyNetworkRequest after removing TelephonyNetworkFactory
+    public void onRequestNetwork(@NonNull NetworkRequest networkRequest) {
         TelephonyNetworkRequest telephonyNetworkRequest = new TelephonyNetworkRequest(
                 networkRequest, PhoneFactory.getDefaultPhone(), mFlags);
         if (!mNetworkRequestList.contains(telephonyNetworkRequest)) {
@@ -1088,7 +1141,13 @@
         }
     }
 
-    private void onReleaseNetwork(NetworkRequest networkRequest) {
+    /**
+     * Called when releasing a network request.
+     *
+     * @param networkRequest The network request to release.
+     */
+    // TODO: Transform to TelephonyNetworkRequest after removing TelephonyNetworkFactory
+    public void onReleaseNetwork(@NonNull NetworkRequest networkRequest) {
         TelephonyNetworkRequest telephonyNetworkRequest = new TelephonyNetworkRequest(
                 networkRequest, PhoneFactory.getDefaultPhone(), mFlags);
         if (mNetworkRequestList.remove(telephonyNetworkRequest)) {
@@ -1102,18 +1161,6 @@
         mDefaultNetworkCallback.mSwitchReason = reason;
     }
 
-    private void collectRequestNetworkMetrics(NetworkRequest networkRequest) {
-        // Request network for MMS will temporary disable the network on default data subscription,
-        // this only happen on multi-sim device.
-        if (mActiveModemCount > 1 && networkRequest.hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_MMS)) {
-            OnDemandDataSwitch onDemandDataSwitch = new OnDemandDataSwitch();
-            onDemandDataSwitch.apn = TelephonyEvent.ApnType.APN_TYPE_MMS;
-            onDemandDataSwitch.state = TelephonyEvent.EventState.EVENT_STATE_START;
-            TelephonyMetrics.getInstance().writeOnDemandDataSwitch(onDemandDataSwitch);
-        }
-    }
-
     private void collectReleaseNetworkMetrics(NetworkRequest networkRequest) {
         // Release network for MMS will recover the network on default data subscription, this only
         // happen on multi-sim device.
@@ -1444,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();
@@ -1562,6 +1613,7 @@
      * If preferred phone changes, or phone activation status changes, registrants
      * will be notified.
      */
+    // TODO: Remove after removing TelephonyNetworkFactory
     public void registerForActivePhoneSwitch(Handler h, int what, Object o) {
         Registrant r = new Registrant(h, what, o);
         mActivePhoneRegistrants.add(r);
@@ -1983,6 +2035,10 @@
         // Notify all registrants
         mActivePhoneRegistrants.notifyRegistrants();
         notifyPreferredDataSubIdChanged();
+        if (mFlags.supportNetworkProvider()) {
+            mPhoneSwitcherCallbacks.forEach(callback -> callback.invokeFromExecutor(
+                    () -> callback.onPreferredDataPhoneIdChanged(phoneId)));
+        }
     }
 
     private boolean isPhoneIdValidForRetry(int phoneId) {
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java
index ca34ca7..7d1746c 100644
--- a/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java
@@ -138,31 +138,19 @@
     public NetworkCapabilities makeNetworkFilter(int subscriptionId) {
         final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IA)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_RCS)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_XCAP)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_MCX)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
                 .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_1)
                 .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_2)
                 .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_3)
                 .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_4)
                 .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_5)
                 .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
-                .setSubscriptionId(subscriptionId).build());
+                                .setSubscriptionId(subscriptionId).build());
+        TelephonyNetworkRequest.getAllSupportedNetworkCapabilities()
+                .forEach(builder::addCapability);
 
         if (mFlags.satelliteInternet()) {
             // TODO: b/328622096 remove the try/catch
@@ -206,7 +194,7 @@
 
     private int getTransportTypeFromNetworkRequest(TelephonyNetworkRequest networkRequest) {
         int transport = AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
-        int capability = networkRequest.getApnTypeNetworkCapability();
+        int capability = networkRequest.getHighestPriorityApnTypeNetworkCapability();
         if (capability >= 0) {
             transport = mAccessNetworksManager
                     .getPreferredTransportByNetworkCapability(capability);
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkProvider.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkProvider.java
new file mode 100644
index 0000000..63edefa
--- /dev/null
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkProvider.java
@@ -0,0 +1,341 @@
+/*
+ * 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.data;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.MatchAllNetworkSpecifier;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkProvider.NetworkOfferCallback;
+import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.os.Handler;
+import android.os.Looper;
+import android.telephony.SubscriptionManager;
+import android.util.ArrayMap;
+import android.util.LocalLog;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.data.PhoneSwitcher.PhoneSwitcherCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.telephony.Rlog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Map;
+
+/**
+ * TelephonyNetworkProvider is a singleton network provider responsible for providing all
+ * telephony related networks including networks on cellular and IWLAN across all active SIMs.
+ */
+public class TelephonyNetworkProvider extends NetworkProvider implements NetworkOfferCallback {
+
+    public final String LOG_TAG = "TNP";
+
+    /** Android feature flags */
+    @NonNull
+    private final FeatureFlags mFlags;
+
+    /** The event handler */
+    @NonNull
+    private final Handler mHandler;
+
+    /** Phone switcher responsible to determine request routing on dual-SIM device */
+    @NonNull
+    private final PhoneSwitcher mPhoneSwitcher;
+
+    /** Network requests map. Key is the network request, value is the phone id it applies to. */
+    private final Map<TelephonyNetworkRequest, Integer> mNetworkRequests = new ArrayMap<>();
+
+    /** Persisted log */
+    @NonNull
+    private final LocalLog mLocalLog = new LocalLog(256);
+
+    /**
+     * Constructor
+     *
+     * @param looper The looper for event handling
+     * @param context The context
+     * @param featureFlags Android feature flags
+     */
+    public TelephonyNetworkProvider(@NonNull Looper looper, @NonNull Context context,
+                                    @NonNull FeatureFlags featureFlags) {
+        super(context, looper, TelephonyNetworkProvider.class.getSimpleName());
+
+        mFlags = featureFlags;
+        mHandler = new Handler(looper);
+        mPhoneSwitcher = PhoneSwitcher.getInstance();
+
+        // Register for subscription changed event.
+        context.getSystemService(SubscriptionManager.class)
+                .addOnSubscriptionsChangedListener(mHandler::post,
+                        new SubscriptionManager.OnSubscriptionsChangedListener() {
+                        @Override
+                        public void onSubscriptionsChanged() {
+                            logl("Subscription changed.");
+                            reevaluateNetworkRequests("subscription changed");
+                        }});
+
+        // Register for preferred data changed event
+        mPhoneSwitcher.registerCallback(new PhoneSwitcherCallback(mHandler::post) {
+                    @Override
+                    public void onPreferredDataPhoneIdChanged(int phoneId) {
+                        logl("Preferred data sub phone id changed to " + phoneId);
+                        reevaluateNetworkRequests("Preferred data subscription changed");
+                    }
+                });
+
+        // Register the provider and tell connectivity service what network offer telephony can
+        // provide
+        ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+        if (cm != null) {
+            cm.registerNetworkProvider(this);
+            NetworkCapabilities caps = makeNetworkFilter();
+            registerNetworkOffer(new NetworkScore.Builder().build(), caps, mHandler::post, this);
+            logl("registerNetworkOffer: " + caps);
+        }
+    }
+
+    /**
+     * Get the phone id for the network request.
+     *
+     * @param request The network request
+     * @return The id of the phone where the network request should route to. If the network request
+     * can't be applied to any phone, {@link SubscriptionManager#INVALID_PHONE_INDEX} will be
+     * returned.
+     */
+    private int getPhoneIdForNetworkRequest(@NonNull TelephonyNetworkRequest request) {
+        for (Phone phone : PhoneFactory.getPhones()) {
+            int phoneId = phone.getPhoneId();
+            if (mPhoneSwitcher.shouldApplyNetworkRequest(request, phoneId)) {
+                // Return here because by design the network request can be only applied to *one*
+                // phone. It's not possible to have two DataNetworkController to attempt to setup
+                // data call for the same network request.
+                return phoneId;
+            }
+        }
+
+        return SubscriptionManager.INVALID_PHONE_INDEX;
+    }
+
+    /**
+     * Called when receiving a network request from connectivity service. This is the entry point
+     * that a network request arrives telephony.
+     *
+     * @param request The network request
+     */
+    @Override
+    public void onNetworkNeeded(@NonNull NetworkRequest request) {
+        TelephonyNetworkRequest networkRequest = new TelephonyNetworkRequest(request, mFlags);
+        if (mNetworkRequests.containsKey(networkRequest)) {
+            loge("Duplicate network request " + networkRequest);
+            return;
+        }
+
+        mPhoneSwitcher.onRequestNetwork(request);
+
+        // Check with PhoneSwitcher to see where to route the request.
+        int phoneId = getPhoneIdForNetworkRequest(networkRequest);
+        if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
+            logl("onNetworkNeeded: phoneId=" + phoneId + ", " + networkRequest);
+            PhoneFactory.getPhone(phoneId).getDataNetworkController()
+                    .addNetworkRequest(networkRequest);
+        } else {
+            logl("onNetworkNeeded: Not applied. " + networkRequest);
+        }
+
+        mNetworkRequests.put(networkRequest, phoneId);
+    }
+
+    /**
+     * Called when connectivity service remove the network request. Note this will not result in
+     * network tear down. Even there is no network request attached to the network, telephony still
+     * relies on {@link NetworkAgent#onNetworkUnwanted()} to tear down the network.
+     *
+     * @param request The released network request
+     *
+     * @see TelephonyNetworkAgent#onNetworkUnwanted()
+     */
+    @Override
+    public void onNetworkUnneeded(@NonNull NetworkRequest request) {
+        TelephonyNetworkRequest networkRequest = mNetworkRequests.keySet().stream()
+                .filter(r -> r.getNativeNetworkRequest().equals(request))
+                .findFirst()
+                .orElse(null);
+        if (networkRequest == null) {
+            loge("onNetworkUnneeded: Cannot find " + request);
+            return;
+        }
+
+        mPhoneSwitcher.onReleaseNetwork(request);
+        int phoneId = mNetworkRequests.remove(networkRequest);
+        Phone phone = PhoneFactory.getPhone(phoneId);
+        if (phone != null) {
+            logl("onNetworkUnneeded: phoneId=" + phoneId + ", " + networkRequest);
+            // Remove the network request from network controller. Note this will not result
+            // in disconnecting the data network.
+            phone.getDataNetworkController().removeNetworkRequest(networkRequest);
+        } else {
+            loge("onNetworkUnneeded: Unable to get phone. phoneId=" + phoneId);
+        }
+    }
+
+    /**
+     * Re-evaluate the existing networks and re-apply to the applicable phone.
+     *
+     * @param reason The reason for re-evaluating network request. Note this can be only used for
+     * debugging message purposes.
+     */
+    private void reevaluateNetworkRequests(@NonNull String reason) {
+        logl("reevaluateNetworkRequests: " + reason + ".");
+        mNetworkRequests.forEach((request, oldPhoneId) -> {
+            int newPhoneId = getPhoneIdForNetworkRequest(request);
+            if (newPhoneId != oldPhoneId) {
+                // We need to move the request from old phone to the new phone. This can happen
+                // when the user changes the default data subscription.
+
+                if (oldPhoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
+                    PhoneFactory.getPhone(oldPhoneId).getDataNetworkController()
+                            .removeNetworkRequest(request);
+                }
+
+                if (newPhoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
+                    PhoneFactory.getPhone(newPhoneId).getDataNetworkController()
+                            .addNetworkRequest(request);
+                }
+
+                logl("Request moved. phoneId " + oldPhoneId + " -> " + newPhoneId + " " + request);
+                mNetworkRequests.put(request, newPhoneId);
+            }
+        });
+    }
+
+    /**
+     * @return The maximal network capabilities that telephony can support.
+     */
+    @NonNull
+    private NetworkCapabilities makeNetworkFilter() {
+        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_IA)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_1)
+                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_2)
+                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_3)
+                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_4)
+                .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_5)
+                // Ideally TelephonyNetworkProvider should only accept TelephonyNetworkSpecifier,
+                // but this network provider is a singleton across all SIMs, and
+                // TelephonyNetworkSpecifier can't accept more than one subscription id, so we let
+                // the provider accepts all different kinds NetworkSpecifier.
+                .setNetworkSpecifier(new MatchAllNetworkSpecifier());
+        TelephonyNetworkRequest.getAllSupportedNetworkCapabilities()
+                .forEach(builder::addCapability);
+
+        // TODO: b/328622096 remove the try/catch
+        try {
+            builder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+        } catch (IllegalArgumentException exception) {
+            log("TRANSPORT_SATELLITE is not supported.");
+        }
+
+        return builder.build();
+    }
+
+    /**
+     * Log debug message to logcat.
+     *
+     * @param s The debug message to log
+     */
+    private void log(@NonNull String s) {
+        Rlog.d(LOG_TAG, s);
+    }
+
+    /**
+     * Log error debug messages to logcat.
+     * @param s The error debug messages
+     */
+    private void loge(@NonNull String s) {
+        Rlog.e(LOG_TAG, s);
+    }
+
+    /**
+     * Log to logcat and persisted local log.
+     *
+     * @param s The debug message to log
+     */
+    private void logl(@NonNull String s) {
+        log(s);
+        mLocalLog.log(s);
+    }
+
+    /**
+     * Dump the state of telephony network provider.
+     *
+     * @param fd File descriptor
+     * @param writer Print writer
+     * @param args Arguments
+     */
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+        pw.println("TelephonyNetworkProvider:");
+        pw.increaseIndent();
+
+        pw.println("mPreferredDataPhoneId=" + mPhoneSwitcher.getPreferredDataPhoneId());
+        int defaultDataSubId = SubscriptionManagerService.getInstance().getDefaultDataSubId();
+        pw.println("DefaultDataSubId=" + defaultDataSubId);
+        pw.println("DefaultDataPhoneId=" + SubscriptionManagerService.getInstance()
+                .getPhoneId(defaultDataSubId));
+
+        pw.println("Registered capabilities: " + makeNetworkFilter());
+        pw.println("Network requests:");
+        pw.increaseIndent();
+        for (Phone phone : PhoneFactory.getPhones()) {
+            pw.println("Phone " + phone.getPhoneId() + ":");
+            pw.increaseIndent();
+            mNetworkRequests.forEach((request, phoneId) -> {
+                if (phoneId == phone.getPhoneId()) {
+                    pw.println(request);
+                }
+            });
+            pw.decreaseIndent();
+        }
+        pw.println("Not applied requests:");
+        pw.increaseIndent();
+        mNetworkRequests.forEach((request, phoneId) -> {
+            if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) {
+                pw.println(request);
+            }
+        });
+        pw.decreaseIndent();
+        pw.decreaseIndent();
+        pw.println();
+        pw.println("Local logs:");
+        pw.increaseIndent();
+        mLocalLog.dump(fd, pw, args);
+        pw.decreaseIndent();
+        pw.decreaseIndent();
+    }
+}
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
index 117eb36..ca1ca21 100644
--- a/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
@@ -137,10 +137,6 @@
                 CAPABILITY_ATTRIBUTE_APN_SETTING | CAPABILITY_ATTRIBUTE_TRAFFIC_DESCRIPTOR_DNN)
     );
 
-    /** The phone instance. */
-    @NonNull
-    private final Phone mPhone;
-
     /**
      * Native network request from the clients. See {@link NetworkRequest};
      */
@@ -164,8 +160,8 @@
     /**
      * Data config manager for retrieving data config.
      */
-    @NonNull
-    private final DataConfigManager mDataConfigManager;
+    @Nullable
+    private DataConfigManager mDataConfigManager;
 
     /**
      * The attached data network. Note that the data network could be in any state. {@code null}
@@ -205,7 +201,19 @@
      */
     public TelephonyNetworkRequest(@NonNull NetworkRequest request, @NonNull Phone phone,
                                    @NonNull FeatureFlags featureFlags) {
-        mPhone = phone;
+        this(request, featureFlags);
+        mDataConfigManager = phone.getDataNetworkController().getDataConfigManager();
+        updatePriority();
+    }
+
+    /**
+     * Constructor
+     *
+     * @param request The native network request from the clients.
+     * @param featureFlags The feature flag
+     */
+    public TelephonyNetworkRequest(@NonNull NetworkRequest request,
+                                   @NonNull FeatureFlags featureFlags) {
         mNativeNetworkRequest = request;
         mFeatureFlags = featureFlags;
 
@@ -222,7 +230,15 @@
         // to satisfy it.
         mState = REQUEST_STATE_UNSATISFIED;
         mCreatedTimeMillis = SystemClock.elapsedRealtime();
-        mDataConfigManager = phone.getDataNetworkController().getDataConfigManager();
+    }
+
+    /**
+     * Update the associated data config manager.
+     *
+     * @param dataConfigManager Data config manager
+     */
+    public void updateDataConfig(@NonNull DataConfigManager dataConfigManager) {
+        mDataConfigManager = dataConfigManager;
         updatePriority();
     }
 
@@ -315,13 +331,15 @@
                 if (mNativeNetworkRequest.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
                         && !mNativeNetworkRequest.hasTransport(
                                 NetworkCapabilities.TRANSPORT_SATELLITE)) {
-                    if (Arrays.stream(getCapabilities()).noneMatch(mDataConfigManager
-                            .getForcedCellularTransportCapabilities()::contains)) {
-                        // If the request is explicitly for the cellular, then the data profile
-                        // needs to support cellular.
-                        if (!dataProfile.getApnSetting().isForInfrastructure(
-                                ApnSetting.INFRASTRUCTURE_CELLULAR)) {
-                            return false;
+                    if (mDataConfigManager != null) {
+                        if (Arrays.stream(getCapabilities()).noneMatch(mDataConfigManager
+                                .getForcedCellularTransportCapabilities()::contains)) {
+                            // If the request is explicitly for the cellular, then the data profile
+                            // needs to support cellular.
+                            if (!dataProfile.getApnSetting().isForInfrastructure(
+                                    ApnSetting.INFRASTRUCTURE_CELLULAR)) {
+                                return false;
+                            }
                         }
                     }
                 } else if (mNativeNetworkRequest.hasTransport(
@@ -371,10 +389,12 @@
      * Update the priority from data config manager.
      */
     public void updatePriority() {
-        mPriority = Arrays.stream(mNativeNetworkRequest.getCapabilities())
-                .map(mDataConfigManager::getNetworkCapabilityPriority)
-                .max()
-                .orElse(0);
+        if (mDataConfigManager != null) {
+            mPriority = Arrays.stream(mNativeNetworkRequest.getCapabilities())
+                    .map(mDataConfigManager::getNetworkCapabilityPriority)
+                    .max()
+                    .orElse(0);
+        }
     }
 
     /**
@@ -385,13 +405,41 @@
      * if there is no APN type capabilities in this network request.
      */
     @NetCapability
-    public int getApnTypeNetworkCapability() {
+    public int getHighestPriorityApnTypeNetworkCapability() {
         if (!hasAttribute(CAPABILITY_ATTRIBUTE_APN_SETTING)) return -1;
+        if (mDataConfigManager == null) return -1;
         return Arrays.stream(getCapabilities()).boxed()
                 .filter(cap -> DataUtils.networkCapabilityToApnType(cap) != ApnSetting.TYPE_NONE)
                 .max(Comparator.comparingInt(mDataConfigManager::getNetworkCapabilityPriority))
                 .orElse(-1);
     }
+
+    /**
+     * A parent set of {@link #getHighestPriorityApnTypeNetworkCapability()}.
+     * Get the network capability from the network request that can lead to data setup. If there are
+     * multiple capabilities, the highest priority one will be returned.
+     *
+     * @return The highest priority traffic descriptor type network capability from this network
+     * request. -1 if there is no traffic descriptor type capabilities in this network request.
+     */
+    @NetCapability
+    public int getHighestPrioritySupportedNetworkCapability() {
+        if (mDataConfigManager == null) return -1;
+        return Arrays.stream(getCapabilities()).boxed()
+                .filter(CAPABILITY_ATTRIBUTE_MAP::containsKey)
+                .max(Comparator.comparingInt(mDataConfigManager::getNetworkCapabilityPriority))
+                .orElse(-1);
+    }
+
+    /**
+     * @return Get all the network capabilities that can lead to data setup.
+     */
+    @NonNull
+    @NetCapability
+    public static List<Integer> getAllSupportedNetworkCapabilities() {
+        return CAPABILITY_ATTRIBUTE_MAP.keySet().stream().toList();
+    }
+
     /**
      * @return The native network request.
      */
@@ -440,7 +488,7 @@
      *
      * @param evaluation The data evaluation result.
      */
-    public void setEvaluation(@NonNull DataEvaluation evaluation) {
+    public void setEvaluation(@Nullable DataEvaluation evaluation) {
         mEvaluation = evaluation;
     }
 
@@ -461,14 +509,6 @@
     }
 
     /**
-     * @return {@code true} if this network request can result in bringing up a metered network.
-     */
-    public boolean isMeteredRequest() {
-        return mDataConfigManager.isAnyMeteredCapability(
-                getCapabilities(), mPhone.getServiceState().getDataRoaming());
-    }
-
-    /**
      * Get Os/App id from the network request.
      *
      * @return Os/App id. {@code null} if the request does not have traffic descriptor based network
@@ -521,7 +561,7 @@
         return "[" + mNativeNetworkRequest + ", mPriority=" + mPriority
                 + ", state=" + requestStateToString(mState)
                 + ", mAttachedDataNetwork=" + (mAttachedDataNetwork != null
-                ? mAttachedDataNetwork.name() : null) + ", isMetered=" + isMeteredRequest()
+                ? mAttachedDataNetwork.name() : null)
                 + ", created time=" + DataUtils.elapsedTimeToString(mCreatedTimeMillis)
                 + ", evaluation result=" + mEvaluation + "]";
     }
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
index fddeb06..681ca12 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
@@ -43,6 +43,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AndroidFuture;
+import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.IDomainSelector;
 import com.android.internal.telephony.ITransportSelectorCallback;
 import com.android.internal.telephony.ITransportSelectorResultCallback;
@@ -50,6 +51,7 @@
 import com.android.internal.telephony.IWwanSelectorResultCallback;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.data.AccessNetworksManager.QualifiedNetworks;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.io.PrintWriter;
@@ -69,7 +71,9 @@
     protected static final int EVENT_SERVICE_CONNECTED = 3;
     protected static final int EVENT_SERVICE_BINDING_TIMEOUT = 4;
     protected static final int EVENT_RESET_NETWORK_SCAN_DONE = 5;
-    protected static final int EVENT_LAST = EVENT_RESET_NETWORK_SCAN_DONE;
+    protected static final int EVENT_TRIGGER_NETWORK_SCAN_DONE = 6;
+    protected static final int EVENT_MODEM_RESET = 7;
+    protected static final int EVENT_LAST = EVENT_MODEM_RESET;
 
     private static final int DEFAULT_BIND_RETRY_TIMEOUT_MS = 4 * 1000;
 
@@ -185,7 +189,10 @@
                     return;
                 }
                 DomainSelectionConnection.this.onSelectionTerminated(cause);
-                dispose();
+                if (!Flags.hangupEmergencyCallForCrossSimRedialing()
+                        || !mIsEmergency || !checkState(STATUS_DOMAIN_SELECTED)) {
+                    dispose();
+                }
             }
         }
     }
@@ -304,6 +311,23 @@
                                 mPendingScanRequest.mScanType, false);
                     }
                     break;
+                case EVENT_TRIGGER_NETWORK_SCAN_DONE:
+                    synchronized (mLock) {
+                        if (checkState(STATUS_DISPOSED) || !checkState(STATUS_WAIT_SCAN_RESULT)) {
+                            return;
+                        }
+                        ar = (AsyncResult) msg.obj;
+                        if (ar != null && ar.exception != null) {
+                            onTriggerNetworkScanError((Integer) ar.userObj,
+                                    ((CommandException) ar.exception).getCommandError());
+                        }
+                    }
+                    break;
+                case EVENT_MODEM_RESET:
+                    synchronized (mLock) {
+                        onModemReset();
+                    }
+                    break;
                 default:
                     loge("handleMessage unexpected msg=" + msg.what);
                     break;
@@ -528,10 +552,13 @@
             if (!mRegisteredRegistrant) {
                 mPhone.registerForEmergencyNetworkScan(mHandler,
                         EVENT_EMERGENCY_NETWORK_SCAN_RESULT, null);
+                mPhone.mCi.registerForModemReset(mHandler, EVENT_MODEM_RESET, null);
                 mRegisteredRegistrant = true;
             }
             setState(STATUS_WAIT_SCAN_RESULT);
-            mPhone.triggerEmergencyNetworkScan(preferredNetworks, scanType, null);
+            mPhone.triggerEmergencyNetworkScan(preferredNetworks, scanType,
+                    mHandler.obtainMessage(EVENT_TRIGGER_NETWORK_SCAN_DONE,
+                            Integer.valueOf(scanType)));
             mPendingScanRequest = null;
         }
     }
@@ -721,6 +748,7 @@
         setState(STATUS_DISPOSED);
         if (mRegisteredRegistrant) {
             mPhone.unregisterForEmergencyNetworkScan(mHandler);
+            mPhone.mCi.unregisterForModemReset(mHandler);
             mRegisteredRegistrant = false;
         }
         onCancel(true);
@@ -745,6 +773,48 @@
         }
     }
 
+    private void onTriggerNetworkScanError(int scanType, CommandException.Error error) {
+        loge("onTriggerNetworkScanError scanType=" + scanType + ", error=" + error);
+
+        if (shouldTerminateCallOnRadioNotAvailable()
+                && error == CommandException.Error.RADIO_NOT_AVAILABLE) {
+            clearState(STATUS_WAIT_SCAN_RESULT);
+            onSelectionTerminated(DisconnectCause.POWER_OFF);
+            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() {
+        loge("onModemReset status=" + mStatus);
+        if (!shouldTerminateCallOnRadioNotAvailable()) {
+            return;
+        }
+        if (checkState(STATUS_DISPOSED) || checkState(STATUS_DOMAIN_SELECTED)) {
+            return;
+        }
+        onSelectionTerminated(DisconnectCause.POWER_OFF);
+        dispose();
+    }
+
+    private boolean shouldTerminateCallOnRadioNotAvailable() {
+        return mIsEmergency && mSelectorType == DomainSelectionService.SELECTOR_TYPE_CALLING;
+    }
+
     /**
      * Get the  preferred transport.
      *
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java
index 410f89b..1b66e54 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionResolver.java
@@ -64,8 +64,7 @@
      *                               to be bound to the domain selection controller.
      */
     public static void make(Context context, String flattenedComponentName) {
-        Log.i(TAG, "make flag=" + Flags.apDomainSelectionEnabled()
-                + ", useOem=" + Flags.useOemDomainSelectionService());
+        Log.i(TAG, "make useOem=" + Flags.useOemDomainSelectionService());
         if (sInstance == null) {
             sInstance = new DomainSelectionResolver(context, flattenedComponentName);
         }
diff --git a/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
index 66b977d..4153c93 100644
--- a/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/EmergencyCallDomainSelectionConnection.java
@@ -47,6 +47,7 @@
 import com.android.internal.telephony.data.AccessNetworksManager;
 import com.android.internal.telephony.data.AccessNetworksManager.QualifiedNetworks;
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
+import com.android.internal.telephony.flags.Flags;
 
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
@@ -198,6 +199,32 @@
         super.cancelSelection();
     }
 
+    @Override
+    public @NonNull CompletableFuture<Integer> reselectDomain(
+        @NonNull DomainSelectionService.SelectionAttributes attr) {
+        if (Flags.hangupEmergencyCallForCrossSimRedialing()) {
+            int disconnectCause = getDisconnectCause();
+            int preciseDisconnectCause = attr.getCsDisconnectCause();
+            if (disconnectCause == android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE) {
+                preciseDisconnectCause = PreciseDisconnectCause.EMERGENCY_TEMP_FAILURE;
+            } else if (disconnectCause
+                    == android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE) {
+                preciseDisconnectCause = PreciseDisconnectCause.EMERGENCY_PERM_FAILURE;
+            }
+            if (preciseDisconnectCause != attr.getCsDisconnectCause()) {
+                attr = EmergencyCallDomainSelectionConnection.getSelectionAttributes(
+                        attr.getSlotIndex(), attr.getSubscriptionId(),
+                        attr.isExitedFromAirplaneMode(), attr.getCallId(),
+                        (attr.getAddress() != null)
+                        ? attr.getAddress().getSchemeSpecificPart() : "",
+                        attr.isTestEmergencyNumber(), preciseDisconnectCause,
+                        attr.getPsDisconnectCause(),
+                        attr.getEmergencyRegistrationResult());
+            }
+        }
+        return super.reselectDomain(attr);
+    }
+
     /**
      * Returns the attributes required to determine the domain for a telephony service.
      *
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
index 167062f..3040c23 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;
@@ -148,7 +155,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 +192,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 +336,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 +355,8 @@
 
                             if (mIsEmergencyCallStartedDuringEmergencySms) {
                                 mIsEmergencyCallStartedDuringEmergencySms = false;
-                                exitEmergencySmsCallbackMode();
+                                exitEmergencySmsCallbackMode(
+                                        STOP_REASON_OUTGOING_EMERGENCY_CALL_INITIATED);
                                 turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
                                         mIsTestEmergencyNumber);
                             }
@@ -409,7 +422,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 +442,7 @@
                     break;
                 }
                 case MSG_EXIT_SCBM: {
-                    exitEmergencySmsCallbackModeAndEmergencyMode();
+                    exitEmergencySmsCallbackModeAndEmergencyMode(STOP_REASON_TIMER_EXPIRED);
                     break;
                 }
                 case MSG_NEW_RINGING_CONNECTION: {
@@ -457,11 +471,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,
+            @NonNull FeatureFlags featureFlags) {
         if (INSTANCE == null) {
             INSTANCE = new EmergencyStateTracker(context, Looper.myLooper(),
-                    isSuplDdsSwitchRequiredForEmergencyCall);
+                    isSuplDdsSwitchRequiredForEmergencyCall, featureFlags);
         }
     }
 
@@ -481,12 +497,12 @@
      * Initializes EmergencyStateTracker.
      */
     private EmergencyStateTracker(Context context, Looper looper,
-            boolean isSuplDdsSwitchRequiredForEmergencyCall) {
+            boolean isSuplDdsSwitchRequiredForEmergencyCall, @NonNull FeatureFlags featureFlags) {
         mEcmExitTimeoutMs = DEFAULT_ECM_EXIT_TIMEOUT_MS;
         mContext = context;
         mHandler = new MyHandler(looper);
         mIsSuplDdsSwitchRequiredForEmergencyCall = isSuplDdsSwitchRequiredForEmergencyCall;
-
+        mFeatureFlags = featureFlags;
         PowerManager pm = context.getSystemService(PowerManager.class);
         mWakeLock = (pm != null) ? pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                 "telephony:" + TAG) : null;
@@ -523,12 +539,13 @@
      * @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) {
+            RadioOnHelper radioOnHelper, long ecmExitTimeoutMs, FeatureFlags featureFlags) {
         mContext = context;
         mHandler = new MyHandler(looper);
         mIsSuplDdsSwitchRequiredForEmergencyCall = isSuplDdsSwitchRequiredForEmergencyCall;
@@ -537,6 +554,7 @@
         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 +592,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()) {
@@ -622,7 +640,7 @@
                     mIsEmergencyCallStartedDuringEmergencySms = false;
                 }
 
-                exitEmergencySmsCallbackMode();
+                exitEmergencySmsCallbackMode(STOP_REASON_OUTGOING_EMERGENCY_CALL_INITIATED);
             }
 
             if (mIsEmergencyCallStartedDuringEmergencySms) {
@@ -1087,7 +1105,7 @@
      * Handles the radio power off request.
      */
     public void onCellularRadioPowerOffRequested() {
-        exitEmergencySmsCallbackModeAndEmergencyMode();
+        exitEmergencySmsCallbackModeAndEmergencyMode(STOP_REASON_UNKNOWN);
         exitEmergencyCallbackMode();
     }
 
@@ -1159,10 +1177,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 +1192,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 +1231,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 +1257,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 +1376,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 +1513,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 +1540,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 +1573,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 +1665,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()) {
@@ -1614,7 +1691,7 @@
                 @Override
                 public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
                     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 +1724,7 @@
                         return false;
                     }
                     return phone.getServiceStateTracker().isRadioOn()
-                            && !satelliteController.isSatelliteEnabled();
+                            && !satelliteController.isSatelliteEnabledOrBeingEnabled();
                 }
 
                 @Override
@@ -1659,7 +1736,7 @@
                     }
                     // onTimeout shall be called only with the Phone for emergency
                     return phone.getServiceStateTracker().isRadioOn()
-                            && !satelliteController.isSatelliteEnabled();
+                            && !satelliteController.isSatelliteEnabledOrBeingEnabled();
                 }
             }, !isTestEmergencyNumber, phone, isTestEmergencyNumber, waitForInServiceTimeout);
         } else {
diff --git a/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
index 306f6bb..e2291c9 100644
--- a/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
+++ b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
@@ -103,8 +103,8 @@
                     && phone == phoneForEmergencyCall, timeoutCallbackInterval);
         }
         powerOnRadio(forEmergencyCall, phoneForEmergencyCall, isTestEmergencyNumber);
-        if (SatelliteController.getInstance().isSatelliteEnabled()) {
-            powerOffSatellite(phoneForEmergencyCall);
+        if (SatelliteController.getInstance().isSatelliteEnabledOrBeingEnabled()) {
+            powerOffSatellite();
         }
     }
 
@@ -150,9 +150,9 @@
      * 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(phoneForEmergencyCall.getSubId(),
+        satelliteController.requestSatelliteEnabled(
                 false /* enableSatellite */, false /* enableDemoMode */, false /* isEmergency */,
                 new IIntegerConsumer.Stub() {
                     @Override
diff --git a/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java b/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java
index fa0610a..f44dc70 100644
--- a/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java
+++ b/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java
@@ -21,7 +21,6 @@
 import android.os.Looper;
 import android.os.Message;
 import android.telephony.ServiceState;
-import android.telephony.SubscriptionManager;
 import android.telephony.satellite.ISatelliteModemStateCallback;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -148,6 +147,21 @@
         public void onSatelliteModemStateChanged(int state) {
             mHandler.obtainMessage(MSG_SATELLITE_ENABLED_CHANGED).sendToTarget();
         }
+
+        @Override
+        public void onEmergencyModeChanged(boolean isEmergency) {
+            Rlog.d(TAG, "onEmergencyModeChanged: ignored " + isEmergency);
+        }
+
+        @Override
+        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.
@@ -221,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();
         }
@@ -392,8 +406,8 @@
                 Rlog.d(TAG, "Trying (again) to turn the radio on and satellite modem off.");
                 mPhone.setRadioPower(true, mForEmergencyCall, mSelectedPhoneForEmergencyCall,
                         false);
-                if (mSatelliteController.isSatelliteEnabled()) {
-                    mSatelliteController.requestSatelliteEnabled(mPhone.getSubId(),
+                if (mSatelliteController.isSatelliteEnabledOrBeingEnabled()) {
+                    mSatelliteController.requestSatelliteEnabled(
                             false /* enableSatellite */, false /* enableDemoMode */,
                             false /* isEmergency*/,
                             new IIntegerConsumer.Stub() {
@@ -497,16 +511,11 @@
     }
 
     private void registerForSatelliteEnabledChanged() {
-        mSatelliteController.registerForSatelliteModemStateChanged(
-                mPhone.getSubId(), mSatelliteCallback);
+        mSatelliteController.registerForSatelliteModemStateChanged(mSatelliteCallback);
     }
 
     private void unregisterForSatelliteEnabledChanged() {
-        int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-        if (mPhone != null) {
-            subId = mPhone.getSubId();
-        }
-        mSatelliteController.unregisterForModemStateChanged(subId, mSatelliteCallback);
+        mSatelliteController.unregisterForModemStateChanged(mSatelliteCallback);
         mHandler.removeMessages(MSG_SATELLITE_ENABLED_CHANGED);
     }
 
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
index 4e773f3..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;
@@ -831,6 +833,7 @@
                         }
                         case CMD_DOWNLOAD_SUBSCRIPTION: {
                             DownloadRequest request = (DownloadRequest) message.obj;
+                            EuiccSession.get().startSession(EuiccSession.DOWNLOAD);
                             mEuiccService.downloadSubscription(slotId,
                                     request.mPortIndex,
                                     request.mSubscription,
@@ -845,6 +848,7 @@
                                                     .onDownloadComplete(result);
                                                 onCommandEnd(callback);
                                             });
+                                            EuiccSession.get().endSession(EuiccSession.DOWNLOAD);
                                         }
                                     });
                             break;
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index 1a5b99e..cc9d793 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -22,10 +22,10 @@
 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;
-import android.app.admin.flags.Flags;
 import android.app.compat.CompatChanges;
 import android.content.ComponentName;
 import android.content.Context;
@@ -71,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;
@@ -622,24 +623,21 @@
             Bundle resolvedBundle, PendingIntent callbackIntent) {
         mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
 
-        boolean callerHasAdminPrivileges = false;
-        if (Flags.esimManagementEnabled()) {
-            callerHasAdminPrivileges = callerCanManageDevicePolicyManagedSubscriptions(
-                    callingPackage);
-            if (callerHasAdminPrivileges && (switchAfterDownload && !shouldAllowSwitchAfterDownload(
-                    callingPackage))) {
-                // Throw error if calling admin does not have privileges to enable
-                // subscription silently after download but switchAfterDownload is passed as true.
-                sendResult(callbackIntent, ERROR, null);
-                return;
-            }
-            if (mContext.getSystemService(UserManager.class).hasUserRestriction(
-                    UserManager.DISALLOW_SIM_GLOBALLY) && !callerHasAdminPrivileges) {
-                // Only admin managed subscriptions are allowed, but the caller is not authorised to
-                // download admin managed subscriptions. Abort.
-                sendResult(callbackIntent, ERROR, null);
-                return;
-            }
+        boolean callerHasAdminPrivileges =
+                callerCanManageDevicePolicyManagedSubscriptions(callingPackage);
+        if (callerHasAdminPrivileges && (switchAfterDownload && !shouldAllowSwitchAfterDownload(
+                callingPackage))) {
+            // Throw error if calling admin does not have privileges to enable
+            // subscription silently after download but switchAfterDownload is passed as true.
+            sendResult(callbackIntent, ERROR, null);
+            return;
+        }
+        if (mContext.getSystemService(UserManager.class).hasUserRestriction(
+                UserManager.DISALLOW_SIM_GLOBALLY) && !callerHasAdminPrivileges) {
+            // Only admin managed subscriptions are allowed, but the caller is not authorised to
+            // download admin managed subscriptions. Abort.
+            sendResult(callbackIntent, ERROR, null);
+            return;
         }
         // Don't try to resolve the port index for apps which are not targeting on T for backward
         // compatibility. instead always use default port 0.
@@ -837,7 +835,7 @@
                 subscription,
                 switchAfterDownload,
                 forceDeactivateSim,
-                resolvedBundle,
+                addCallingPackageToBundle(resolvedBundle, callingPackage),
                 new EuiccConnector.DownloadCommandCallback() {
                     @Override
                     public void onDownloadComplete(DownloadSubscriptionResult result) {
@@ -936,6 +934,13 @@
                 });
     }
 
+    private static Bundle addCallingPackageToBundle(
+                @Nullable Bundle resolvedBundle, String callingPackage) {
+        resolvedBundle = resolvedBundle == null ? new Bundle() : resolvedBundle;
+        resolvedBundle.putString(EuiccService.EXTRA_PACKAGE_NAME, callingPackage);
+        return resolvedBundle;
+    }
+
     /**
      * Blocking call to {@link EuiccService#onGetEuiccProfileInfoList} of the eUICC with card ID
      * {@code cardId}.
@@ -1070,9 +1075,7 @@
     public void deleteSubscription(int cardId, int subscriptionId, String callingPackage,
             PendingIntent callbackIntent) {
         boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
-        boolean callerIsAdmin =
-                Flags.esimManagementEnabled()
-                        && callerCanManageDevicePolicyManagedSubscriptions(callingPackage);
+        boolean callerIsAdmin = callerCanManageDevicePolicyManagedSubscriptions(callingPackage);
         mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
 
         long token = Binder.clearCallingIdentity();
@@ -1088,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 */);
@@ -1207,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;
@@ -1286,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();
             }
         }
@@ -1557,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;
@@ -1745,7 +1748,7 @@
 
     private void refreshSubscriptionsOwnership(boolean isCallerAdmin, String callingPackage,
             int cardId, Set<Integer> subscriptionsBefore) {
-        if (Flags.esimManagementEnabled() && isCallerAdmin) {
+        if (isCallerAdmin) {
             // Mark the newly downloaded subscriptions as being owned by an admin so
             // that actions for that subscription can be restricted,
             // and the admin is limited to effecting only these subscriptions.
@@ -1854,7 +1857,12 @@
         if (bestComponent != null) {
             intent.setPackage(bestComponent.packageName);
         }
-        mContext.sendBroadcast(intent, permission.WRITE_EMBEDDED_SUBSCRIPTIONS);
+        if (mFeatureFlags.hsumBroadcast()) {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                    permission.WRITE_EMBEDDED_SUBSCRIPTIONS);
+        } else {
+            mContext.sendBroadcast(intent, permission.WRITE_EMBEDDED_SUBSCRIPTIONS);
+        }
     }
 
     @Nullable
@@ -1871,9 +1879,6 @@
     }
 
     private Set<Integer> getCurrentEmbeddedSubscriptionIds(int cardId) {
-        if (!Flags.esimManagementEnabled()) {
-            return new ArraySet<>();
-        }
         List<SubscriptionInfo> subscriptionInfos =
                 mSubscriptionManager.getAvailableSubscriptionInfoList();
         int subCount = (subscriptionInfos != null) ? subscriptionInfos.size() : 0;
@@ -2064,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;
             }
         }
@@ -2133,8 +2138,7 @@
                     if (subInfo.isEmbedded()
                             && subInfo.getCardId() == cardId
                             && (!usePortIndex || subInfo.getPortIndex() == targetPortIndex)
-                            && mSubscriptionManager.canManageSubscription(
-                            subInfo, callingPackage)) {
+                            && canManageSubscription(subInfo, callingPackage)) {
                         return true;
                     }
                 }
@@ -2153,7 +2157,7 @@
         } else {
             for (SubscriptionInfo subInfo : subInfoList) {
                 if (subInfo.isEmbedded()
-                        && mSubscriptionManager.canManageSubscription(subInfo, callingPackage)) {
+                        && canManageSubscription(subInfo, callingPackage)) {
                     return true;
                 }
             }
@@ -2388,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
new file mode 100644
index 0000000..f17789f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/euicc/EuiccSession.java
@@ -0,0 +1,183 @@
+/*
+ * 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.euicc;
+
+import android.annotation.Nullable;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.uicc.euicc.apdu.ApduSender;
+import com.android.telephony.Rlog;
+
+import java.util.Set;
+
+/**
+ * A eUICC transaction session aims to optimize multiple back-to-back EuiccPort API calls by only
+ * open and close a logical channel once.
+ *
+ * <p>This class is thread-safe.
+ */
+public class EuiccSession {
+    private static final String TAG = "EuiccSession";
+
+    // **** Well known session IDs, see #startSession() ****
+    public static final String DOWNLOAD = "DOWNLOAD";
+
+    @GuardedBy("EuiccSession.class")
+    private static EuiccSession sInstance;
+
+    public static synchronized EuiccSession get() {
+        if (sInstance == null) {
+            sInstance = new EuiccSession();
+        }
+        return sInstance;
+    }
+
+    @GuardedBy("this")
+    private final Set<String> mSessions = new ArraySet<>();
+
+    @GuardedBy("this")
+    private final Set<ApduSender> mApduSenders = new ArraySet<>();
+
+    /**
+     * Marks the start of a eUICC transaction session.
+     *
+     * <p>A session means a long-open logical channel (see {@link ApduSender}) used to
+     * send multiple APDUs for one action e.g. {@link EuiccController#downloadSubscription()}.
+     * Those APDUs can be send by one or multiple {@link EuiccCardController} methods.
+     *
+     * <p>Ideally a session should correespond to one phoneId and hence just one logical channel.
+     * But many {@link EuiccCardController} methods uses first available port and is not specific
+     * to a phoneId. So EuiccController cannot choose one phoneId to use. Hence a session has to
+     * be not specific to phoneId, i.e. for DSDS device both phoneId's will be in a session.
+     *
+     * <p>If called multiple times with different {@code sessionId}'s, the session is truly closed
+     * when the all sessions are ended. See {@link #endSession()}.
+     *
+     * @param sessionId The session ID.
+     */
+    public void startSession(String sessionId) {
+        if (!Flags.optimizationApduSender()) {
+            // Other methods in this class is no-op if no session started.
+            // Do not add flag to other methods, so if the flag gets turned off,
+            // the session can be ended properly.
+            return;
+        }
+        Rlog.i(TAG, "startSession: " + sessionId);
+        synchronized(this) {
+            mSessions.add(sessionId);
+        }
+    }
+
+    /** Returns {@code true} if there is at least one session ongoing. */
+    public boolean hasSession() {
+        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()}).
+     *
+     * <p>No-op if no session ongoing (see {@link #hasSession()}).
+     *
+     * @param apduSender The ApduSender that will open the channel.
+     */
+    public void noteChannelOpen(ApduSender apduSender) {
+        Rlog.i(TAG, "noteChannelOpen: " + apduSender);
+        synchronized(this) {
+            if (hasSessionInternal()) {
+                mApduSenders.add(apduSender);
+            }
+        }
+    }
+
+    /**
+     * 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}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 = 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);
+                mApduSenders.clear();
+            }
+        }
+        for (ApduSender apduSender : apduSenders) {
+            apduSender.closeAnyOpenChannel();
+        }
+    }
+
+    /**
+     * 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/GsmInboundSmsHandler.java b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
index 907f158..bf8098f 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
@@ -38,6 +38,7 @@
 import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.SmsStorageMonitor;
 import com.android.internal.telephony.VisualVoicemailSmsFilter;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.uicc.UsimServiceTable;
 
 /**
@@ -58,8 +59,8 @@
      * Create a new GSM inbound SMS handler.
      */
     private GsmInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor,
-            Phone phone, Looper looper) {
-        super("GsmInboundSmsHandler", context, storageMonitor, phone, looper);
+            Phone phone, Looper looper, FeatureFlags featureFlags) {
+        super("GsmInboundSmsHandler", context, storageMonitor, phone, looper, featureFlags);
         phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null);
         mDataDownloadHandler = new UsimDataDownloadHandler(phone.mCi, phone.getPhoneId());
         mCellBroadcastServiceManager.enable();
@@ -129,9 +130,10 @@
      * Wait for state machine to enter startup state. We can't send any messages until then.
      */
     public static GsmInboundSmsHandler makeInboundSmsHandler(Context context,
-            SmsStorageMonitor storageMonitor, Phone phone, Looper looper) {
+            SmsStorageMonitor storageMonitor, Phone phone, Looper looper,
+            FeatureFlags featureFlags) {
         GsmInboundSmsHandler handler =
-                new GsmInboundSmsHandler(context, storageMonitor, phone, looper);
+                new GsmInboundSmsHandler(context, storageMonitor, phone, looper, featureFlags);
         handler.start();
         return handler;
     }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java b/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java
index 234723f..f2b5dee 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsNrSaModeHandler.java
@@ -27,6 +27,8 @@
 import static android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
 
+import static com.android.internal.telephony.CommandsInterface.IMS_MMTEL_CAPABILITY_VOICE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -52,21 +54,19 @@
 public class ImsNrSaModeHandler extends Handler{
 
     public static final String TAG = "ImsNrSaModeHandler";
-    public static final String MMTEL_FEATURE_TAG =
-            "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\"";
 
     private static final int MSG_PRECISE_CALL_STATE_CHANGED = 101;
-    private static final int MSG_REQUEST_IS_VONR_ENABLED = 102;
-    private static final int MSG_RESULT_IS_VONR_ENABLED = 103;
+    private static final int MSG_RESULT_IS_VONR_ENABLED = 102;
 
     private final @NonNull ImsPhone mPhone;
     private @Nullable CarrierConfigManager mCarrierConfigManager;
 
     private @NrSaDisablePolicy int mNrSaDisablePolicy;
     private boolean mIsNrSaDisabledForWfc;
-    private boolean mIsVowifiRegistered;
+    private boolean mIsWifiRegistered;
     private boolean mIsInImsCall;
     private boolean mIsNrSaSupported;
+    private boolean mIsVoiceCapable;
 
     private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
             (slotIndex, subId, carrierId, specificCarrierId) -> setNrSaDisablePolicy(subId);
@@ -100,37 +100,16 @@
      */
     public void onImsRegistered(
             @ImsRegistrationTech int imsRadioTech, @NonNull Set<String> featureTags) {
-        if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_NONE) {
+        if (!mIsNrSaSupported || mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_NONE) {
             return;
         }
 
         Log.d(TAG, "onImsRegistered: ImsRegistrationTech = " + imsRadioTech);
 
-        boolean isVowifiRegChanged = false;
-
-        if (isVowifiRegistered() && imsRadioTech != REGISTRATION_TECH_IWLAN) {
-            setVowifiRegStatus(false);
-            isVowifiRegChanged = true;
-        } else if (!isVowifiRegistered() && imsRadioTech == REGISTRATION_TECH_IWLAN
-                && featureTags.contains(MMTEL_FEATURE_TAG)) {
-            setVowifiRegStatus(true);
-            isVowifiRegChanged = true;
-        }
-
-        if (isVowifiRegChanged) {
-            if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED) {
-                setNrSaMode(!isVowifiRegistered());
-            } else if ((mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED
-                    || mNrSaDisablePolicy
-                    == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED)
-                    && isImsCallOngoing()) {
-                if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED) {
-                    requestIsVonrEnabled(!isVowifiRegistered());
-                    return;
-                }
-
-                setNrSaMode(!isVowifiRegistered());
-            }
+        final boolean isNewWifiRegistered = imsRadioTech == REGISTRATION_TECH_IWLAN;
+        if (isWifiRegistered() != isNewWifiRegistered) {
+            setWifiRegStatus(isNewWifiRegistered);
+            calculateAndControlNrSaIfNeeded();
         }
     }
 
@@ -141,27 +120,15 @@
      */
     public void onImsUnregistered(
             @ImsRegistrationTech int imsRadioTech) {
-        if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_NONE
-                || imsRadioTech != REGISTRATION_TECH_IWLAN || !isVowifiRegistered()) {
+        if (!mIsNrSaSupported || mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_NONE
+                || imsRadioTech != REGISTRATION_TECH_IWLAN || !isWifiRegistered()) {
             return;
         }
 
         Log.d(TAG, "onImsUnregistered : ImsRegistrationTech = " + imsRadioTech);
 
-        setVowifiRegStatus(false);
-
-        if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED) {
-            setNrSaMode(true);
-        } else if ((mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED
-                || mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED)
-                && isImsCallOngoing()) {
-            if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED) {
-                requestIsVonrEnabled(true);
-                return;
-            }
-
-            setNrSaMode(true);
-        }
+        setWifiRegStatus(false);
+        calculateAndControlNrSaIfNeeded();
     }
 
     /**
@@ -182,13 +149,23 @@
             isImsCallStatusChanged = true;
         }
 
-        if (isVowifiRegistered() && isImsCallStatusChanged) {
-            if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED) {
-                requestIsVonrEnabled(!isImsCallOngoing());
-                return;
-            }
+        if (isWifiRegistered() && isImsCallStatusChanged) {
+            calculateAndControlNrSaIfNeeded();
+        }
+    }
 
-            setNrSaMode(!isImsCallOngoing());
+    /**
+     * Updates Capability.
+     */
+    public void updateImsCapability(int capabilities) {
+        if (!mIsNrSaSupported || mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_NONE) {
+            return;
+        }
+
+        boolean isVoiceCapable = (IMS_MMTEL_CAPABILITY_VOICE & capabilities) != 0;
+        if (mIsVoiceCapable != isVoiceCapable) {
+            mIsVoiceCapable = isVoiceCapable;
+            calculateAndControlNrSaIfNeeded();
         }
     }
 
@@ -200,11 +177,6 @@
             case MSG_PRECISE_CALL_STATE_CHANGED :
                 onPreciseCallStateChanged();
                 break;
-            case MSG_REQUEST_IS_VONR_ENABLED :
-                Log.d(TAG, "request isVoNrEnabled");
-                mPhone.getDefaultPhone().mCi.isVoNrEnabled(
-                        obtainMessage(MSG_RESULT_IS_VONR_ENABLED, msg.obj), null);
-                break;
             case MSG_RESULT_IS_VONR_ENABLED :
                 ar = (AsyncResult) msg.obj;
 
@@ -212,8 +184,9 @@
                     boolean vonrEnabled = ((Boolean) ar.result).booleanValue();
 
                     Log.d(TAG, "result isVoNrEnabled = " + vonrEnabled);
-                    if (!vonrEnabled) {
-                        setNrSaMode(((Boolean) ar.userObj).booleanValue());
+                    if (isWifiCallingOngoing() && !vonrEnabled) {
+                        // If still WiFi calling is ongoing and VoNR is disabled, disable NR SA.
+                        setNrSaMode(false);
                     }
                 }
 
@@ -262,14 +235,18 @@
         if (mPhone.getSubId() == subId && mCarrierConfigManager != null) {
             PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId(),
                     KEY_NR_SA_DISABLE_POLICY_INT, KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
-            mNrSaDisablePolicy = bundle.getInt(KEY_NR_SA_DISABLE_POLICY_INT);
             int[] nrAvailabilities = bundle.getIntArray(KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY);
             mIsNrSaSupported = nrAvailabilities != null
                     && Arrays.stream(nrAvailabilities).anyMatch(
                             value -> value == CARRIER_NR_AVAILABILITY_SA);
 
-            Log.d(TAG, "setNrSaDisablePolicy : NrSaDisablePolicy = "
-                    + mNrSaDisablePolicy + ", IsNrSaSupported = "  + mIsNrSaSupported);
+            if (!mIsNrSaSupported) {
+                return;
+            }
+
+            mNrSaDisablePolicy = bundle.getInt(KEY_NR_SA_DISABLE_POLICY_INT);
+
+            Log.d(TAG, "setNrSaDisablePolicy : NrSaDisablePolicy = " + mNrSaDisablePolicy);
 
             if (mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED
                     || mNrSaDisablePolicy == NR_SA_DISABLE_POLICY_WFC_ESTABLISHED) {
@@ -280,27 +257,20 @@
         }
     }
 
-    private void requestIsVonrEnabled(boolean onOrOff) {
-        Message msg = obtainMessage(MSG_REQUEST_IS_VONR_ENABLED, onOrOff);
-        msg.sendToTarget();
-    }
-
     private void setNrSaMode(boolean onOrOff) {
-        if (mIsNrSaSupported) {
-            mPhone.getDefaultPhone().setN1ModeEnabled(onOrOff, null);
-            Log.i(TAG, "setNrSaMode : " + onOrOff);
+        mPhone.getDefaultPhone().setN1ModeEnabled(onOrOff, null);
+        Log.i(TAG, "setNrSaMode : " + onOrOff);
 
-            setNrSaDisabledForWfc(!onOrOff);
-        }
+        setNrSaDisabledForWfc(!onOrOff);
     }
 
     /**
-     * Sets VoWiFi reg status.
+     * Sets WiFi reg status.
      */
     @VisibleForTesting
-    public void setVowifiRegStatus(boolean registered) {
-        Log.d(TAG, "setVowifiRegStatus : " + registered);
-        mIsVowifiRegistered = registered;
+    public void setWifiRegStatus(boolean registered) {
+        Log.d(TAG, "setWifiRegStatus : " + registered);
+        mIsWifiRegistered = registered;
     }
 
     /**
@@ -313,8 +283,8 @@
     }
 
     @VisibleForTesting
-    public boolean isVowifiRegistered() {
-        return mIsVowifiRegistered;
+    public boolean isWifiRegistered() {
+        return mIsWifiRegistered;
     }
 
     @VisibleForTesting
@@ -322,8 +292,7 @@
         return mIsInImsCall;
     }
 
-    @VisibleForTesting
-    public boolean isNrSaDisabledForWfc() {
+    private boolean isNrSaDisabledForWfc() {
         return mIsNrSaDisabledForWfc;
     }
 
@@ -353,4 +322,56 @@
 
         return false;
     }
+
+    private void calculateAndControlNrSaIfNeeded() {
+        switch (mNrSaDisablePolicy) {
+            case NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED:
+                if (isNrSaDisabledForWfc() == isWifiRegisteredForVoice()) {
+                    // NR SA is already disabled or condition is not met for disabling NR SA.
+                    // So, no need for further action
+                    return;
+                }
+
+                // Disable NR SA if VoWiFi registered otherwise enable
+                setNrSaMode(!isWifiRegisteredForVoice());
+                return;
+            case NR_SA_DISABLE_POLICY_WFC_ESTABLISHED:
+                if (isNrSaDisabledForWfc() == isWifiCallingOngoing()) {
+                    // NR SA is already disabled or condition is not met for disabling NR SA.
+                    // So, no need for further action
+                    return;
+                }
+
+                // Disable NR SA if VoWiFi call established otherwise enable
+                setNrSaMode(!isWifiCallingOngoing());
+                return;
+            case NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED:
+                if (isNrSaDisabledForWfc() == isWifiCallingOngoing()) {
+                    // NR SA is already disabled or condition is not met for disabling NR SA.
+                    // So, no need for further action
+                    return;
+                }
+
+                if (isWifiCallingOngoing()) {
+                    // Query whether VoNR is enabled or not.
+                    mPhone.getDefaultPhone().mCi.isVoNrEnabled(
+                            obtainMessage(MSG_RESULT_IS_VONR_ENABLED), null);
+                    return;
+                }
+
+                // Enable NR SA if there are no VoWiFi calls.
+                setNrSaMode(true);
+                return;
+            default:
+                break;
+        }
+    }
+
+    private boolean isWifiRegisteredForVoice() {
+        return isWifiRegistered() && mIsVoiceCapable;
+    }
+
+    private boolean isWifiCallingOngoing() {
+        return isWifiRegistered() && mIsVoiceCapable && isImsCallOngoing();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index 10cbe77..22b8a75 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -1016,8 +1016,8 @@
 
         // Only look at the Network portion for mmi
         String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString);
-        ImsPhoneMmiCode mmi =
-                ImsPhoneMmiCode.newFromDialString(networkPortion, this, wrappedCallback);
+        ImsPhoneMmiCode mmi =  ImsPhoneMmiCode.newFromDialString(networkPortion, this,
+                wrappedCallback, mFeatureFlags);
         if (DBG) logd("dialInternal: dialing w/ mmi '" + mmi + "'...");
 
         if (mmi == null) {
@@ -1440,12 +1440,13 @@
     }
 
     @Override
-    public void sendUssdResponse(String ussdMessge) {
+    public void sendUssdResponse(String ussdMessage) {
         logd("sendUssdResponse");
-        ImsPhoneMmiCode mmi = ImsPhoneMmiCode.newFromUssdUserInput(ussdMessge, this);
+        ImsPhoneMmiCode mmi = ImsPhoneMmiCode.newFromUssdUserInput(ussdMessage, this,
+                mFeatureFlags);
         mPendingMMIs.add(mmi);
         mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
-        mmi.sendUssd(ussdMessge);
+        mmi.sendUssd(ussdMessage);
     }
 
     public void sendUSSD(String ussdString, Message response) {
@@ -1575,14 +1576,12 @@
                 // also, discard if there is no message to present
                 ImsPhoneMmiCode mmi;
                 mmi = ImsPhoneMmiCode.newNetworkInitiatedUssd(ussdMessage,
-                        isUssdRequest,
-                        this);
+                        isUssdRequest, this, mFeatureFlags);
                 onNetworkInitiatedUssd(mmi);
         } else if (isUssdError) {
             ImsPhoneMmiCode mmi;
             mmi = ImsPhoneMmiCode.newNetworkInitiatedUssd(ussdMessage,
-                    true,
-                    this);
+                    true, this, mFeatureFlags);
             mmi.onUssdFinishedError();
         }
     }
@@ -2763,7 +2762,7 @@
     }
 
     /**
-     * Update IMS registration information to modem.
+     * Update IMS registration information to modem and other modules.
      *
      * @param capabilities indicate MMTEL capability such as VOICE, VIDEO and SMS.
      */
@@ -2784,6 +2783,8 @@
             mDefaultPhone.mCi.updateImsRegistrationInfo(mImsRegistrationState,
                     mImsRegistrationTech, 0, capabilities, null);
             mNotifiedRegisteredState = true;
+
+            mImsNrSaModeHandler.updateImsCapability(capabilities);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index e73eafd..d47f05b 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -70,6 +70,7 @@
 import android.os.RegistrantList;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.sysprop.TelephonyProperties;
@@ -1543,7 +1544,11 @@
         Intent intent = new Intent(intentAction);
         intent.putExtra(ImsManager.EXTRA_PHONE_ID, mPhone.getPhoneId());
         if (mPhone != null && mPhone.getContext() != null) {
-            mPhone.getContext().sendBroadcast(intent);
+            if (mFeatureFlags.hsumBroadcast()) {
+                mPhone.getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
+            } else {
+                mPhone.getContext().sendBroadcast(intent);
+            }
         }
     }
 
@@ -1792,7 +1797,8 @@
                                 dialArgs.intentExtras);
                     }
                 };
-                EmergencyStateTracker.getInstance().exitEmergencyCallbackMode(onComplete);
+                EmergencyStateTracker.getInstance().exitEmergencyCallbackMode(onComplete,
+                        TelephonyManager.STOP_REASON_OUTGOING_NORMAL_CALL_INITIATED);
             } else {
                 try {
                     getEcbmInterface().exitEmergencyCallbackMode();
@@ -2878,6 +2884,14 @@
         ImsPhoneConnection conn = findConnection(imsCall);
         boolean rejectCall = false;
 
+        if (mFeatureFlags.preventHangupDuringCallMerge()) {
+            if (imsCall != null && imsCall.isCallSessionMergePending()) {
+                if (DBG) log("hangup call failed during call merge");
+
+                throw new CallStateException("can not hangup during call merge");
+            }
+        }
+
         String logResult = "(undefined)";
         if (call == mRingingCall) {
             logResult = "(ringing) hangup incoming";
@@ -3260,6 +3274,12 @@
         int cause = DisconnectCause.ERROR_UNSPECIFIED;
 
         int code = maybeRemapReasonCode(reasonInfo);
+
+        if (mFeatureFlags.remapDisconnectCauseSipRequestCancelled() &&
+                code == ImsReasonInfo.CODE_SIP_REQUEST_CANCELLED) {
+            return DisconnectCause.NORMAL;
+        }
+
         switch (code) {
             case ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL:
                 return DisconnectCause.IMS_SIP_ALTERNATE_EMERGENCY_CALL;
@@ -4666,8 +4686,14 @@
                     configChangedIntent.putExtra(ImsConfig.EXTRA_CHANGED_ITEM, item);
                     configChangedIntent.putExtra(ImsConfig.EXTRA_NEW_VALUE, value);
                     if (mPhone != null && mPhone.getContext() != null) {
-                        mPhone.getContext().sendBroadcast(configChangedIntent,
-                                Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+                        if (mFeatureFlags.hsumBroadcast()) {
+                            mPhone.getContext().sendBroadcastAsUser(configChangedIntent,
+                                    UserHandle.ALL,
+                                    Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+                        } else {
+                            mPhone.getContext().sendBroadcast(configChangedIntent,
+                                    Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+                        }
                     }
                 }
 
@@ -4951,7 +4977,7 @@
             }
             case EVENT_SUPP_SERVICE_INDICATION: {
                 ar = (AsyncResult) msg.obj;
-                ImsPhoneMmiCode mmiCode = new ImsPhoneMmiCode(mPhone);
+                ImsPhoneMmiCode mmiCode = new ImsPhoneMmiCode(mPhone, mFeatureFlags);
                 try {
                     mmiCode.setIsSsInfo(true);
                     mmiCode.processImsSsData(ar);
@@ -6327,8 +6353,13 @@
         configChangedIntent.putExtra(ImsConfig.EXTRA_CHANGED_ITEM, item);
         configChangedIntent.putExtra(ImsConfig.EXTRA_NEW_VALUE, value);
         if (mPhone != null && mPhone.getContext() != null) {
-            mPhone.getContext().sendBroadcast(
-                    configChangedIntent, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+            if (mFeatureFlags.hsumBroadcast()) {
+                mPhone.getContext().sendBroadcastAsUser(configChangedIntent, UserHandle.ALL,
+                        Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+            } else {
+                mPhone.getContext().sendBroadcast(
+                        configChangedIntent, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+            }
         }
     }
 }
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
index 25fa8a2..d573f5d 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
@@ -63,6 +63,7 @@
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.MmiCode;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.gsm.GsmMmiCode;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.telephony.Rlog;
@@ -165,6 +166,9 @@
     private static final int NUM_PRESENTATION_ALLOWED     = 0;
     private static final int NUM_PRESENTATION_RESTRICTED  = 1;
 
+    // Using same value with CallForwardEditPreference#DEFAULT_NO_REPLY_TIMER_FOR_CFNRY
+    private static final int DEFAULT_NO_REPLY_TIMER_FOR_CFNRY = 20;
+
     //***** Supplementary Service Query Bundle Keys
     // Used by IMS Service layer to put supp. serv. query
     // responses into the ssInfo Bundle.
@@ -245,6 +249,8 @@
     private static final int MATCH_GROUP_DIALING_NUMBER = 12;
     static private String[] sTwoDigitNumberPattern;
 
+    private final FeatureFlags mFeatureFlags;
+
     //***** Public Class methods
 
     /**
@@ -262,12 +268,13 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @VisibleForTesting
-    public static ImsPhoneMmiCode newFromDialString(String dialString, ImsPhone phone) {
-       return newFromDialString(dialString, phone, null);
+    public static ImsPhoneMmiCode newFromDialString(String dialString, ImsPhone phone,
+            FeatureFlags featureFlags) {
+        return newFromDialString(dialString, phone, null, featureFlags);
     }
 
-    static ImsPhoneMmiCode newFromDialString(String dialString,
-                                             ImsPhone phone, ResultReceiver wrappedCallback) {
+    static ImsPhoneMmiCode newFromDialString(String dialString, ImsPhone phone,
+            ResultReceiver wrappedCallback, FeatureFlags featureFlags) {
         Matcher m;
         ImsPhoneMmiCode ret = null;
 
@@ -287,7 +294,7 @@
 
         // Is this formatted like a standard supplementary service code?
         if (m.matches()) {
-            ret = new ImsPhoneMmiCode(phone);
+            ret = new ImsPhoneMmiCode(phone, featureFlags);
             ret.mPoundString = makeEmptyNull(m.group(MATCH_GROUP_POUND_STRING));
             ret.mAction = makeEmptyNull(m.group(MATCH_GROUP_ACTION));
             ret.mSc = makeEmptyNull(m.group(MATCH_GROUP_SERVICE_CODE));
@@ -305,7 +312,7 @@
             if (ret.mDialingNumber != null &&
                     ret.mDialingNumber.endsWith("#") &&
                     dialString.endsWith("#")){
-                ret = new ImsPhoneMmiCode(phone);
+                ret = new ImsPhoneMmiCode(phone, featureFlags);
                 ret.mPoundString = dialString;
             }
         } else if (dialString.endsWith("#")) {
@@ -313,7 +320,7 @@
             // "Entry of any characters defined in the 3GPP TS 23.038 [8] Default Alphabet
             // (up to the maximum defined in 3GPP TS 24.080 [10]), followed by #SEND".
 
-            ret = new ImsPhoneMmiCode(phone);
+            ret = new ImsPhoneMmiCode(phone, featureFlags);
             ret.mPoundString = dialString;
         } else if (GsmMmiCode.isTwoDigitShortCode(phone.getContext(), phone.getSubId(),
                 dialString)) {
@@ -321,7 +328,7 @@
             ret = null;
         } else if (isShortCode(dialString, phone)) {
             // this may be a short code, as defined in TS 22.030, 6.5.3.2
-            ret = new ImsPhoneMmiCode(phone);
+            ret = new ImsPhoneMmiCode(phone, featureFlags);
             ret.mDialingNumber = dialString;
         }
         return ret;
@@ -348,10 +355,10 @@
     }
 
     public static ImsPhoneMmiCode newNetworkInitiatedUssd(String ussdMessage,
-            boolean isUssdRequest, ImsPhone phone) {
+            boolean isUssdRequest, ImsPhone phone, FeatureFlags featureFlags) {
         ImsPhoneMmiCode ret;
 
-        ret = new ImsPhoneMmiCode(phone);
+        ret = new ImsPhoneMmiCode(phone, featureFlags);
 
         ret.mMessage = ussdMessage;
         ret.mIsUssdRequest = isUssdRequest;
@@ -368,10 +375,11 @@
         return ret;
     }
 
-    static ImsPhoneMmiCode newFromUssdUserInput(String ussdMessge, ImsPhone phone) {
-        ImsPhoneMmiCode ret = new ImsPhoneMmiCode(phone);
+    static ImsPhoneMmiCode newFromUssdUserInput(String ussdMessage, ImsPhone phone,
+            FeatureFlags featureFlags) {
+        ImsPhoneMmiCode ret = new ImsPhoneMmiCode(phone, featureFlags);
 
-        ret.mMessage = ussdMessge;
+        ret.mMessage = ussdMessage;
         ret.mState = State.PENDING;
         ret.mIsPendingUSSD = true;
 
@@ -584,13 +592,15 @@
 
     //***** Constructor
 
-    public ImsPhoneMmiCode(ImsPhone phone) {
+    public ImsPhoneMmiCode(ImsPhone phone, FeatureFlags featureFlags) {
         // The telephony unit-test cases may create ImsPhoneMmiCode's
         // in secondary threads
         super(phone.getHandler().getLooper());
         mPhone = phone;
         mContext = phone.getContext();
         mIccRecords = mPhone.mDefaultPhone.getIccRecords();
+
+        mFeatureFlags = featureFlags;
     }
 
     //***** MmiCode implementation
@@ -907,7 +917,19 @@
                 String dialingNumber = mSia;
                 int reason = scToCallForwardReason(mSc);
                 int serviceClass = siToServiceClass(mSib);
-                int time = siToTime(mSic);
+                int time;
+
+                if (mFeatureFlags.useCarrierConfigForCfnryTimeViaMmi()) {
+                    // If the code is CFNRy and time is null(empty)
+                    // use the default time value from CarrierConfig
+                    if (mSc.equals(SC_CFNRy) && isEmptyOrNull(mSic)) {
+                        time = getCfnryTime();
+                    } else {
+                        time = siToTime(mSic);
+                    }
+                } else {
+                    time = siToTime(mSic);
+                }
 
                 if (isInterrogate()) {
                     mPhone.getCallForwardingOption(reason, serviceClass,
@@ -1147,6 +1169,27 @@
         }
     }
 
+    private int getCfnryTime() {
+        CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class);
+        if (configManager == null) {
+            return DEFAULT_NO_REPLY_TIMER_FOR_CFNRY;
+        }
+
+        PersistableBundle carrierConfig = configManager.getConfigForSubId(mPhone.getSubId());
+        if (carrierConfig == null
+                || !carrierConfig.getBoolean(
+                        CarrierConfigManager.KEY_SUPPORT_NO_REPLY_TIMER_FOR_CFNRY_BOOL, true)) {
+            return DEFAULT_NO_REPLY_TIMER_FOR_CFNRY;
+        }
+
+        int time = carrierConfig.getInt(
+                CarrierConfigManager.KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT,
+                DEFAULT_NO_REPLY_TIMER_FOR_CFNRY);
+
+        Rlog.d(LOG_TAG, "getCfnryTime: " + time);
+        return time;
+    }
+
     private boolean isUssdOverImsAllowed() {
         if (mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_allow_ussd_over_ims)) {
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index a83cd06..e43bf3c 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -1147,7 +1147,8 @@
                 session.vonrEnabled,
                 session.isNtn,
                 session.supportsBusinessCallComposer,
-                session.callComposerStatus);
+                session.callComposerStatus,
+                session.preciseCallStateOnSetup);
 
     }
 
@@ -1474,7 +1475,12 @@
                 satelliteController.countOfDatagramTypeKeepAliveFail,
                 satelliteController.countOfAllowedSatelliteAccess,
                 satelliteController.countOfDisallowedSatelliteAccess,
-                satelliteController.countOfSatelliteAccessCheckFail);
+                satelliteController.countOfSatelliteAccessCheckFail,
+                satelliteController.isProvisioned,
+                satelliteController.carrierId,
+                satelliteController.countOfSatelliteAllowedStateChangedEvents,
+                satelliteController.countOfSuccessfulLocationQueries,
+                satelliteController.countOfFailedLocationQueries);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteSession satelliteSession) {
@@ -1492,7 +1498,11 @@
                 satelliteSession.countOfIncomingDatagramSuccess,
                 satelliteSession.countOfIncomingDatagramFailed,
                 satelliteSession.isDemoMode,
-                satelliteSession.maxNtnSignalStrengthLevel);
+                satelliteSession.maxNtnSignalStrengthLevel,
+                satelliteSession.carrierId,
+                satelliteSession.countOfSatelliteNotificationDisplayed,
+                satelliteSession.countOfAutoExitDueToScreenOff,
+                satelliteSession.countOfAutoExitDueToTnNetwork);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteIncomingDatagram stats) {
@@ -1501,7 +1511,8 @@
                 stats.resultCode,
                 stats.datagramSizeBytes,
                 stats.datagramTransferTimeMillis,
-                stats.isDemoMode);
+                stats.isDemoMode,
+                stats.carrierId);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteOutgoingDatagram stats) {
@@ -1511,7 +1522,8 @@
                 stats.resultCode,
                 stats.datagramSizeBytes,
                 stats.datagramTransferTimeMillis,
-                stats.isDemoMode);
+                stats.isDemoMode,
+                stats.carrierId);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteProvision stats) {
@@ -1520,7 +1532,8 @@
                 stats.resultCode,
                 stats.provisioningTimeSec,
                 stats.isProvisionRequest,
-                stats.isCanceled);
+                stats.isCanceled,
+                stats.carrierId);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteSosMessageRecommender stats) {
@@ -1533,7 +1546,9 @@
                 stats.count,
                 stats.isMultiSim,
                 stats.recommendingHandoverType,
-                stats.isSatelliteAllowedInCurrentLocation);
+                stats.isSatelliteAllowedInCurrentLocation,
+                stats.isWifiConnected,
+                stats.carrierId);
     }
 
     private static StatsEvent buildStatsEvent(DataNetworkValidation stats) {
@@ -1578,7 +1593,9 @@
                 stats.countOfSatelliteNotificationDisplayed,
                 stats.satelliteSessionGapMinSec,
                 stats.satelliteSessionGapAvgSec,
-                stats.satelliteSessionGapMaxSec);
+                stats.satelliteSessionGapMaxSec,
+                stats.carrierId,
+                stats.isDeviceEntitled);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteEntitlement stats) {
@@ -1610,7 +1627,9 @@
                 stats.isEmergency,
                 stats.resultCode,
                 stats.countryCodes,
-                stats.configDataSource);
+                stats.configDataSource,
+                stats.carrierId,
+                stats.triggeringEvent);
     }
 
     /** 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 12dab7a..9f74890 100644
--- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -771,6 +771,14 @@
         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);
     }
@@ -853,24 +861,24 @@
     /** 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.configDataSource = stats.configDataSource;
+            existingStats.countOfEntitlementStatusQueryRequest +=
+                    stats.countOfEntitlementStatusQueryRequest;
+            existingStats.countOfSatelliteConfigUpdateRequest +=
+                    stats.countOfSatelliteConfigUpdateRequest;
+            existingStats.countOfSatelliteNotificationDisplayed +=
+                    stats.countOfSatelliteNotificationDisplayed;
+            existingStats.satelliteSessionGapMinSec = stats.satelliteSessionGapMinSec;
+            existingStats.satelliteSessionGapAvgSec = stats.satelliteSessionGapAvgSec;
+            existingStats.satelliteSessionGapMaxSec = stats.satelliteSessionGapMaxSec;
+            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;
-
-        mAtoms.carrierRoamingSatelliteControllerStats = atomArray;
         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
     }
 
@@ -2331,7 +2339,12 @@
                     && stats.countOfIncomingDatagramSuccess == key.countOfIncomingDatagramSuccess
                     && stats.countOfIncomingDatagramFailed == key.countOfIncomingDatagramFailed
                     && stats.isDemoMode == key.isDemoMode
-                    && stats.maxNtnSignalStrengthLevel == key.maxNtnSignalStrengthLevel) {
+                    && stats.maxNtnSignalStrengthLevel == key.maxNtnSignalStrengthLevel
+                    && stats.carrierId == key.carrierId
+                    && stats.countOfSatelliteNotificationDisplayed
+                    == key.countOfSatelliteNotificationDisplayed
+                    && stats.countOfAutoExitDueToScreenOff == key.countOfAutoExitDueToScreenOff
+                    && stats.countOfAutoExitDueToTnNetwork == key.countOfAutoExitDueToTnNetwork) {
                 return stats;
             }
         }
@@ -2350,7 +2363,9 @@
                     && stats.isImsRegistered == key.isImsRegistered
                     && stats.cellularServiceState == key.cellularServiceState
                     && stats.isMultiSim == key.isMultiSim
-                    && stats.recommendingHandoverType == key.recommendingHandoverType) {
+                    && stats.recommendingHandoverType == key.recommendingHandoverType
+                    && stats.isWifiConnected == key.isWifiConnected
+                    && stats.carrierId == key.carrierId) {
                 return stats;
             }
         }
@@ -2375,6 +2390,21 @@
     }
 
     /**
+     * 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) {
+                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 c2b2753..71c1bf3 100644
--- a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
+++ b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
@@ -17,6 +17,9 @@
 package com.android.internal.telephony.metrics;
 
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE;
+import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
+
+import static com.android.internal.telephony.satellite.SatelliteConstants.TRIGGERING_EVENT_UNKNOWN;
 
 import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteManager;
@@ -37,6 +40,7 @@
 import com.android.telephony.Rlog;
 
 import java.util.Arrays;
+import java.util.Optional;
 
 /** Tracks Satellite metrics for each phone */
 public class SatelliteStats {
@@ -91,6 +95,11 @@
         private final int mCountOfAllowedSatelliteAccess;
         private final int mCountOfDisallowedSatelliteAccess;
         private final int mCountOfSatelliteAccessCheckFail;
+        private static boolean sIsProvisioned;
+        private static int sCarrierId = UNKNOWN_CARRIER_ID;
+        private final int mCountOfSatelliteAllowedStateChangedEvents;
+        private final int mCountOfSuccessfulLocationQueries;
+        private final int mCountOfFailedLocationQueries;
 
         private SatelliteControllerParams(Builder builder) {
             this.mCountOfSatelliteServiceEnablementsSuccess =
@@ -136,6 +145,23 @@
                     builder.mCountOfDisallowedSatelliteAccess;
             this.mCountOfSatelliteAccessCheckFail =
                     builder.mCountOfSatelliteAccessCheckFail;
+
+            // isProvisioned value should be updated only when it is meaningful.
+            if (builder.mIsProvisioned.isPresent()) {
+                this.sIsProvisioned = builder.mIsProvisioned.get();
+            }
+
+            // Carrier ID value should be updated only when it is meaningful.
+            if (builder.mCarrierId.isPresent()) {
+                this.sCarrierId = builder.mCarrierId.get();
+            }
+
+            this.mCountOfSatelliteAllowedStateChangedEvents =
+                    builder.mCountOfSatelliteAllowedStateChangedEvents;
+            this.mCountOfSuccessfulLocationQueries =
+                    builder.mCountOfSuccessfulLocationQueries;
+            this.mCountOfFailedLocationQueries =
+                    builder.mCountOfFailedLocationQueries;
         }
 
         public int getCountOfSatelliteServiceEnablementsSuccess() {
@@ -250,6 +276,26 @@
             return mCountOfSatelliteAccessCheckFail;
         }
 
+        public boolean isProvisioned() {
+            return sIsProvisioned;
+        }
+
+        public int getCarrierId() {
+            return sCarrierId;
+        }
+
+        public int getCountOfSatelliteAllowedStateChangedEvents() {
+            return mCountOfSatelliteAllowedStateChangedEvents;
+        }
+
+        public int getCountOfSuccessfulLocationQueries() {
+            return mCountOfSuccessfulLocationQueries;
+        }
+
+        public int getCountOfFailedLocationQueries() {
+            return mCountOfFailedLocationQueries;
+        }
+
         /**
          * A builder class to create {@link SatelliteControllerParams} data structure class
          */
@@ -282,6 +328,11 @@
             private int mCountOfAllowedSatelliteAccess = 0;
             private int mCountOfDisallowedSatelliteAccess = 0;
             private int mCountOfSatelliteAccessCheckFail = 0;
+            private Optional<Boolean> mIsProvisioned = Optional.empty();
+            private Optional<Integer> mCarrierId = Optional.empty();
+            private int mCountOfSatelliteAllowedStateChangedEvents = 0;
+            private int mCountOfSuccessfulLocationQueries = 0;
+            private int mCountOfFailedLocationQueries = 0;
 
             /**
              * Sets countOfSatelliteServiceEnablementsSuccess value of {@link SatelliteController}
@@ -561,6 +612,55 @@
             }
 
             /**
+             * Sets isProvisioned value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setIsProvisioned(boolean isProvisioned) {
+                this.mIsProvisioned = Optional.of(isProvisioned);
+                return this;
+            }
+
+            /**
+             * Sets Carrier ID value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCarrierId(int carrierId) {
+                this.mCarrierId = Optional.of(carrierId);
+                return this;
+            }
+
+            /**
+             * Sets countOfSatelliteAllowedStateChangedEvents value of {@link SatelliteController}
+             * atom
+             * then returns Builder class
+             */
+            public Builder setCountOfSatelliteAllowedStateChangedEvents(
+                    int countOfSatelliteAllowedStateChangedEvents) {
+                this.mCountOfSatelliteAllowedStateChangedEvents =
+                        countOfSatelliteAllowedStateChangedEvents;
+                return this;
+            }
+
+            /**
+             * Sets countOfSuccessfulLocationQueries value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfSuccessfulLocationQueries(
+                    int countOfSuccessfulLocationQueries) {
+                this.mCountOfSuccessfulLocationQueries = countOfSuccessfulLocationQueries;
+                return this;
+            }
+
+            /**
+             * Sets countOfFailedLocationQueries value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfFailedLocationQueries(int countOfFailedLocationQueries) {
+                this.mCountOfFailedLocationQueries = countOfFailedLocationQueries;
+                return this;
+            }
+
+            /**
              * Returns ControllerParams, which contains whole component of
              * {@link SatelliteController} atom
              */
@@ -609,6 +709,12 @@
                     + ", countOfAllowedSatelliteAccess=" + mCountOfAllowedSatelliteAccess
                     + ", countOfDisallowedSatelliteAccess=" + mCountOfDisallowedSatelliteAccess
                     + ", countOfSatelliteAccessCheckFail=" + mCountOfSatelliteAccessCheckFail
+                    + ", isProvisioned=" + sIsProvisioned
+                    + ", carrierId=" + sCarrierId
+                    + ", countOfSatelliteAllowedStateChangedEvents="
+                    + mCountOfSatelliteAllowedStateChangedEvents
+                    + ", countOfSuccessfulLocationQueries=" + mCountOfSuccessfulLocationQueries
+                    + ", countOfFailedLocationQueries=" + mCountOfFailedLocationQueries
                     + ")";
         }
     }
@@ -630,6 +736,11 @@
         private final int mCountOfIncomingDatagramFailed;
         private final boolean mIsDemoMode;
         private final @NtnSignalStrength.NtnSignalStrengthLevel int mMaxNtnSignalStrengthLevel;
+        private final int mCarrierId;
+        private final int mCountOfSatelliteNotificationDisplayed;
+        private final int mCountOfAutoExitDueToScreenOff;
+        private final int mCountOfAutoExitDueToTnNetwork;
+
 
         private SatelliteSessionParams(Builder builder) {
             this.mSatelliteServiceInitializationResult =
@@ -646,6 +757,11 @@
             this.mCountOfIncomingDatagramFailed = builder.mCountOfIncomingDatagramFailed;
             this.mIsDemoMode = builder.mIsDemoMode;
             this.mMaxNtnSignalStrengthLevel = builder.mMaxNtnSignalStrengthLevel;
+            this.mCarrierId = builder.mCarrierId;
+            this.mCountOfSatelliteNotificationDisplayed =
+                    builder.mCountOfSatelliteNotificationDisplayed;
+            this.mCountOfAutoExitDueToScreenOff = builder.mCountOfAutoExitDueToScreenOff;
+            this.mCountOfAutoExitDueToTnNetwork = builder.mCountOfAutoExitDueToTnNetwork;
         }
 
         public int getSatelliteServiceInitializationResult() {
@@ -696,6 +812,22 @@
             return mMaxNtnSignalStrengthLevel;
         }
 
+        public int getCarrierId() {
+            return mCarrierId;
+        }
+
+        public int getCountOfSatelliteNotificationDisplayed() {
+            return mCountOfSatelliteNotificationDisplayed;
+        }
+
+        public int getCountOfAutoExitDueToScreenOff() {
+            return mCountOfAutoExitDueToScreenOff;
+        }
+
+        public int getCountOfAutoExitDueToTnNetwork() {
+            return mCountOfAutoExitDueToTnNetwork;
+        }
+
         /**
          * A builder class to create {@link SatelliteSessionParams} data structure class
          */
@@ -713,6 +845,10 @@
             private boolean mIsDemoMode = false;
             private @NtnSignalStrength.NtnSignalStrengthLevel int mMaxNtnSignalStrengthLevel =
                     NTN_SIGNAL_STRENGTH_NONE;
+            private int mCarrierId = UNKNOWN_CARRIER_ID;
+            private int mCountOfSatelliteNotificationDisplayed = -1;
+            private int mCountOfAutoExitDueToScreenOff = -1;
+            private int mCountOfAutoExitDueToTnNetwork = -1;
 
             /**
              * Sets satelliteServiceInitializationResult value of {@link SatelliteSession}
@@ -788,13 +924,49 @@
                 return this;
             }
 
-            /** Sets the max ntn signal strength for the satellite session */
+            /** Sets the max ntn signal strength for the satellite session. */
             public Builder setMaxNtnSignalStrengthLevel(
                     @NtnSignalStrength.NtnSignalStrengthLevel int maxNtnSignalStrengthLevel) {
                 this.mMaxNtnSignalStrengthLevel = maxNtnSignalStrengthLevel;
                 return this;
             }
 
+            /** Sets the currently active NB-IoT NTN carrier ID. */
+            public Builder setCarrierId(int carrierId) {
+                this.mCarrierId = carrierId;
+                return this;
+            }
+
+            /**
+             * Sets Total number of times the user is notified that the device is eligible for
+             * satellite service for this session.
+             */
+            public Builder setCountOfSatelliteNotificationDisplayed(
+                    int countOfSatelliteNotificationDisplayed) {
+                this.mCountOfSatelliteNotificationDisplayed = countOfSatelliteNotificationDisplayed;
+                return this;
+            }
+
+            /**
+             * Sets Total number of times exit P2P message service automatically due to screen is
+             * off and timer is expired.
+             */
+            public Builder setCountOfAutoExitDueToScreenOff(
+                    int countOfAutoExitDueToScreenOff) {
+                this.mCountOfAutoExitDueToScreenOff = countOfAutoExitDueToScreenOff;
+                return this;
+            }
+
+            /**
+             * Sets Total number of times times exit P2P message service automatically due to
+             * scan TN network.
+             */
+            public Builder setCountOfAutoExitDueToTnNetwork(
+                    int countOfAutoExitDueToTnNetwork) {
+                this.mCountOfAutoExitDueToTnNetwork = countOfAutoExitDueToTnNetwork;
+                return this;
+            }
+
             /**
              * Returns SessionParams, which contains whole component of
              * {@link SatelliteSession} atom
@@ -820,6 +992,11 @@
                     + ", CountOfIncomingDatagramFailed=" + mCountOfIncomingDatagramFailed
                     + ", IsDemoMode=" + mIsDemoMode
                     + ", MaxNtnSignalStrengthLevel=" + mMaxNtnSignalStrengthLevel
+                    + ", CarrierId=" + mCarrierId
+                    + ", CountOfSatelliteNotificationDisplayed"
+                    + mCountOfSatelliteNotificationDisplayed
+                    + ", CountOfAutoExitDueToScreenOff" + mCountOfAutoExitDueToScreenOff
+                    + ", CountOfAutoExitDueToTnNetwork" + mCountOfAutoExitDueToTnNetwork
                     + ")";
         }
     }
@@ -833,12 +1010,14 @@
         private final int mDatagramSizeBytes;
         private final long mDatagramTransferTimeMillis;
         private final boolean mIsDemoMode;
+        private final int mCarrierId;
 
         private SatelliteIncomingDatagramParams(Builder builder) {
             this.mResultCode = builder.mResultCode;
             this.mDatagramSizeBytes = builder.mDatagramSizeBytes;
             this.mDatagramTransferTimeMillis = builder.mDatagramTransferTimeMillis;
             this.mIsDemoMode = builder.mIsDemoMode;
+            this.mCarrierId = builder.mCarrierId;
         }
 
         public int getResultCode() {
@@ -857,6 +1036,10 @@
             return mIsDemoMode;
         }
 
+        public int getCarrierId() {
+            return mCarrierId;
+        }
+
         /**
          * A builder class to create {@link SatelliteIncomingDatagramParams} data structure class
          */
@@ -865,6 +1048,7 @@
             private int mDatagramSizeBytes = -1;
             private long mDatagramTransferTimeMillis = -1;
             private boolean mIsDemoMode = false;
+            private int mCarrierId = UNKNOWN_CARRIER_ID;
 
             /**
              * Sets resultCode value of {@link SatelliteIncomingDatagram} atom
@@ -902,6 +1086,12 @@
                 return this;
             }
 
+            /** Sets the currently active NB-IoT NTN carrier ID. */
+            public Builder setCarrierId(int carrierId) {
+                this.mCarrierId = carrierId;
+                return this;
+            }
+
             /**
              * Returns IncomingDatagramParams, which contains whole component of
              * {@link SatelliteIncomingDatagram} atom
@@ -919,6 +1109,7 @@
                     + ", datagramSizeBytes=" + mDatagramSizeBytes
                     + ", datagramTransferTimeMillis=" + mDatagramTransferTimeMillis
                     + ", isDemoMode=" + mIsDemoMode
+                    + ", CarrierId=" + mCarrierId
                     + ")";
         }
     }
@@ -933,6 +1124,7 @@
         private final int mDatagramSizeBytes;
         private final long mDatagramTransferTimeMillis;
         private final boolean mIsDemoMode;
+        private final int mCarrierId;
 
         private SatelliteOutgoingDatagramParams(Builder builder) {
             this.mDatagramType = builder.mDatagramType;
@@ -940,6 +1132,7 @@
             this.mDatagramSizeBytes = builder.mDatagramSizeBytes;
             this.mDatagramTransferTimeMillis = builder.mDatagramTransferTimeMillis;
             this.mIsDemoMode = builder.mIsDemoMode;
+            this.mCarrierId = builder.mCarrierId;
         }
 
         public int getDatagramType() {
@@ -962,6 +1155,10 @@
             return mIsDemoMode;
         }
 
+        public int getCarrierId() {
+            return mCarrierId;
+        }
+
         /**
          * A builder class to create {@link SatelliteOutgoingDatagramParams} data structure class
          */
@@ -971,6 +1168,7 @@
             private int mDatagramSizeBytes = -1;
             private long mDatagramTransferTimeMillis = -1;
             private boolean mIsDemoMode = false;
+            private int mCarrierId = UNKNOWN_CARRIER_ID;
 
             /**
              * Sets datagramType value of {@link SatelliteOutgoingDatagram} atom
@@ -1017,6 +1215,12 @@
                 return this;
             }
 
+            /** Sets the currently active NB-IoT NTN carrier ID. */
+            public Builder setCarrierId(int carrierId) {
+                this.mCarrierId = carrierId;
+                return this;
+            }
+
             /**
              * Returns OutgoingDatagramParams, which contains whole component of
              * {@link SatelliteOutgoingDatagram} atom
@@ -1035,6 +1239,7 @@
                     + ", datagramSizeBytes=" + mDatagramSizeBytes
                     + ", datagramTransferTimeMillis=" + mDatagramTransferTimeMillis
                     + ", isDemoMode=" + mIsDemoMode
+                    + ", CarrierId=" + mCarrierId
                     + ")";
         }
     }
@@ -1048,12 +1253,14 @@
         private final int mProvisioningTimeSec;
         private final boolean mIsProvisionRequest;
         private final boolean mIsCanceled;
+        private final int mCarrierId;
 
         private SatelliteProvisionParams(Builder builder) {
             this.mResultCode = builder.mResultCode;
             this.mProvisioningTimeSec = builder.mProvisioningTimeSec;
             this.mIsProvisionRequest = builder.mIsProvisionRequest;
             this.mIsCanceled = builder.mIsCanceled;
+            this.mCarrierId = builder.mCarrierId;
         }
 
         public int getResultCode() {
@@ -1072,6 +1279,10 @@
             return mIsCanceled;
         }
 
+        public int getCarrierId() {
+            return mCarrierId;
+        }
+
         /**
          * A builder class to create {@link SatelliteProvisionParams} data structure class
          */
@@ -1080,6 +1291,7 @@
             private int mProvisioningTimeSec = -1;
             private boolean mIsProvisionRequest = false;
             private boolean mIsCanceled = false;
+            private int mCarrierId = UNKNOWN_CARRIER_ID;
 
             /**
              * Sets resultCode value of {@link SatelliteProvision} atom
@@ -1117,6 +1329,12 @@
                 return this;
             }
 
+            /** Sets the currently active NB-IoT NTN carrier ID. */
+            public Builder setCarrierId(int carrierId) {
+                this.mCarrierId = carrierId;
+                return this;
+            }
+
             /**
              * Returns ProvisionParams, which contains whole component of
              * {@link SatelliteProvision} atom
@@ -1133,7 +1351,9 @@
                     + "resultCode=" + mResultCode
                     + ", provisioningTimeSec=" + mProvisioningTimeSec
                     + ", isProvisionRequest=" + mIsProvisionRequest
-                    + ", isCanceled" + mIsCanceled + ")";
+                    + ", isCanceled" + mIsCanceled
+                    + ", CarrierId=" + mCarrierId
+                    + ")";
         }
     }
 
@@ -1149,6 +1369,8 @@
         private final boolean mIsMultiSim;
         private final int mRecommendingHandoverType;
         private final boolean mIsSatelliteAllowedInCurrentLocation;
+        private final boolean mIsWifiConnected;
+        private final int mCarrierId;
 
         private SatelliteSosMessageRecommenderParams(Builder builder) {
             this.mIsDisplaySosMessageSent = builder.mIsDisplaySosMessageSent;
@@ -1159,6 +1381,8 @@
             this.mRecommendingHandoverType = builder.mRecommendingHandoverType;
             this.mIsSatelliteAllowedInCurrentLocation =
                     builder.mIsSatelliteAllowedInCurrentLocation;
+            this.mIsWifiConnected = builder.mIsWifiConnected;
+            this.mCarrierId = builder.mCarrierId;
         }
 
         public boolean isDisplaySosMessageSent() {
@@ -1189,6 +1413,14 @@
             return mIsSatelliteAllowedInCurrentLocation;
         }
 
+        public boolean isWifiConnected() {
+            return mIsWifiConnected;
+        }
+
+        public int getCarrierId() {
+            return mCarrierId;
+        }
+
         /**
          * A builder class to create {@link SatelliteProvisionParams} data structure class
          */
@@ -1200,6 +1432,8 @@
             private boolean mIsMultiSim = false;
             private int mRecommendingHandoverType = -1;
             private boolean mIsSatelliteAllowedInCurrentLocation = false;
+            private boolean mIsWifiConnected = false;
+            private int mCarrierId = UNKNOWN_CARRIER_ID;
 
 
             /**
@@ -1268,6 +1502,24 @@
             }
 
             /**
+             * Sets whether Wi-Fi is connected value of {@link SatelliteSosMessageRecommender} atom
+             * then returns Builder class
+             */
+            public Builder setIsWifiConnected(boolean isWifiConnected) {
+                this.mIsWifiConnected = isWifiConnected;
+                return this;
+            }
+
+            /**
+             * Sets carrier ID value of {@link SatelliteSosMessageRecommender} atom then returns
+             * Builder class.
+             */
+            public Builder setCarrierId(int carrierId) {
+                this.mCarrierId = carrierId;
+                return this;
+            }
+
+            /**
              * Returns SosMessageRecommenderParams, which contains whole component of
              * {@link SatelliteSosMessageRecommenderParams} atom
              */
@@ -1287,7 +1539,10 @@
                     + ", isMultiSim=" + mIsMultiSim
                     + ", recommendingHandoverType=" + mRecommendingHandoverType
                     + ", isSatelliteAllowedInCurrentLocation="
-                    + mIsSatelliteAllowedInCurrentLocation + ")";
+                    + mIsSatelliteAllowedInCurrentLocation
+                    + ", isWifiConnected=" + mIsWifiConnected
+                    + ", carrierId=" + mCarrierId
+                    + ")";
         }
     }
 
@@ -1614,6 +1869,8 @@
         private final int mSatelliteSessionGapMinSec;
         private final int mSatelliteSessionGapAvgSec;
         private final int mSatelliteSessionGapMaxSec;
+        private static int sCarrierId;
+        private static boolean sIsDeviceEntitled;
 
         private CarrierRoamingSatelliteControllerStatsParams(Builder builder) {
             this.mConfigDataSource = builder.mConfigDataSource;
@@ -1626,6 +1883,16 @@
             this.mSatelliteSessionGapMinSec = builder.mSatelliteSessionGapMinSec;
             this.mSatelliteSessionGapAvgSec = builder.mSatelliteSessionGapAvgSec;
             this.mSatelliteSessionGapMaxSec = builder.mSatelliteSessionGapMaxSec;
+
+            // Carrier ID value should be updated only when it is meaningful.
+            if (builder.mCarrierId.isPresent()) {
+                this.sCarrierId = builder.mCarrierId.get();
+            }
+
+            // isDeviceEntitled value should be updated only when it is meaningful.
+            if (builder.mIsDeviceEntitled.isPresent()) {
+                this.sIsDeviceEntitled = builder.mIsDeviceEntitled.get();
+            }
         }
 
         public int getConfigDataSource() {
@@ -1657,6 +1924,14 @@
             return mSatelliteSessionGapMaxSec;
         }
 
+        public int getCarrierId() {
+            return sCarrierId;
+        }
+
+        public boolean isDeviceEntitled() {
+            return sIsDeviceEntitled;
+        }
+
         /**
          * A builder class to create {@link CarrierRoamingSatelliteControllerStatsParams}
          * data structure class
@@ -1669,6 +1944,8 @@
             private int mSatelliteSessionGapMinSec = 0;
             private int mSatelliteSessionGapAvgSec = 0;
             private int mSatelliteSessionGapMaxSec = 0;
+            private Optional<Integer> mCarrierId = Optional.empty();
+            private Optional<Boolean> mIsDeviceEntitled = Optional.empty();
 
             /**
              * Sets configDataSource value of {@link CarrierRoamingSatelliteControllerStats} atom
@@ -1736,6 +2013,18 @@
                 return this;
             }
 
+            /** Sets the currently active NB-IoT NTN carrier ID. */
+            public Builder setCarrierId(int carrierId) {
+                this.mCarrierId = Optional.of(carrierId);
+                return this;
+            }
+
+            /** Sets whether the device is currently entitled or not. */
+            public Builder setIsDeviceEntitled(boolean isDeviceEntitled) {
+                this.mIsDeviceEntitled = Optional.of(isDeviceEntitled);
+                return this;
+            }
+
             /**
              * Returns CarrierRoamingSatelliteControllerStatsParams, which contains whole component
              * of {@link CarrierRoamingSatelliteControllerStats} atom
@@ -1759,6 +2048,8 @@
                     + ", satelliteSessionGapMinSec=" + mSatelliteSessionGapMinSec
                     + ", satelliteSessionGapAvgSec=" + mSatelliteSessionGapAvgSec
                     + ", satelliteSessionGapMaxSec=" + mSatelliteSessionGapMaxSec
+                    + ", carrierId=" + sCarrierId
+                    + ", isDeviceEntitled=" + sIsDeviceEntitled
                     + ")";
         }
     }
@@ -1990,6 +2281,8 @@
         private final @SatelliteManager.SatelliteResult int mResultCode;
         private final String[] mCountryCodes;
         private final @SatelliteConstants.ConfigDataSource int mConfigDataSource;
+        private final int mCarrierId;
+        private final int mTriggeringEvent;
 
         private SatelliteAccessControllerParams(Builder builder) {
             this.mAccessControlType = builder.mAccessControlType;
@@ -2001,6 +2294,8 @@
             this.mResultCode = builder.mResultCode;
             this.mCountryCodes = builder.mCountryCodes;
             this.mConfigDataSource = builder.mConfigDataSource;
+            this.mCarrierId = builder.mCarrierId;
+            this.mTriggeringEvent = builder.mTriggeringEvent;
         }
 
         public @SatelliteConstants.AccessControlType int getAccessControlType() {
@@ -2039,6 +2334,14 @@
             return mConfigDataSource;
         }
 
+        public int getCarrierId() {
+            return mCarrierId;
+        }
+
+        @SatelliteConstants.TriggeringEvent public int getTriggeringEvent() {
+            return mTriggeringEvent;
+        }
+
         /**
          * A builder class to create {@link SatelliteAccessControllerParams} data structure class
          */
@@ -2052,6 +2355,9 @@
             private @SatelliteManager.SatelliteResult int mResultCode;
             private String[] mCountryCodes;
             private @SatelliteConstants.ConfigDataSource int mConfigDataSource;
+            private int mCarrierId = UNKNOWN_CARRIER_ID;
+            private @SatelliteConstants.TriggeringEvent int mTriggeringEvent =
+                    TRIGGERING_EVENT_UNKNOWN;
 
             /**
              * Sets AccessControlType value of {@link #SatelliteAccessController}
@@ -2113,6 +2419,19 @@
                 return this;
             }
 
+            /** Sets the currently active NB-IoT NTN carrier ID. */
+            public Builder setCarrierId(int carrierId) {
+                this.mCarrierId = carrierId;
+                return this;
+            }
+
+            /** Sets the triggering evenr for current satellite access controller metric. */
+            public Builder setTriggeringEvent(
+                    @SatelliteConstants.TriggeringEvent int triggeringEvent) {
+                this.mTriggeringEvent = triggeringEvent;
+                return this;
+            }
+
             /**
              * Returns AccessControllerParams, which contains whole component of
              * {@link #SatelliteAccessController} atom
@@ -2135,6 +2454,8 @@
                     + ", ResultCode=" + mResultCode
                     + ", CountryCodes=" + Arrays.toString(mCountryCodes)
                     + ", ConfigDataSource=" + mConfigDataSource
+                    + ", CarrierId=" + mCarrierId
+                    + ", TriggeringEvent=" + mTriggeringEvent
                     + ")";
         }
     }
@@ -2175,6 +2496,16 @@
         proto.countOfDemoModeIncomingDatagramFail = param.getCountOfDemoModeIncomingDatagramFail();
         proto.countOfDatagramTypeKeepAliveSuccess = param.getCountOfDatagramTypeKeepAliveSuccess();
         proto.countOfDatagramTypeKeepAliveFail = param.getCountOfDatagramTypeKeepAliveFail();
+        proto.countOfAllowedSatelliteAccess = param.getCountOfAllowedSatelliteAccess();
+        proto.countOfDisallowedSatelliteAccess = param.getCountOfDisallowedSatelliteAccess();
+        proto.countOfSatelliteAccessCheckFail = param.getCountOfSatelliteAccessCheckFail();
+        proto.isProvisioned = param.isProvisioned();
+        proto.carrierId = param.getCarrierId();
+        proto.countOfSatelliteAllowedStateChangedEvents =
+                param.getCountOfSatelliteAllowedStateChangedEvents();
+        proto.countOfSuccessfulLocationQueries = param.getCountOfSuccessfulLocationQueries();
+        proto.countOfFailedLocationQueries = param.getCountOfFailedLocationQueries();
+
         mAtomsStorage.addSatelliteControllerStats(proto);
     }
 
@@ -2195,6 +2526,11 @@
         proto.countOfIncomingDatagramFailed = param.getCountOfOutgoingDatagramFailed();
         proto.isDemoMode = param.getIsDemoMode();
         proto.maxNtnSignalStrengthLevel = param.getMaxNtnSignalStrengthLevel();
+        proto.carrierId = param.getCarrierId();
+        proto.countOfSatelliteNotificationDisplayed =
+                param.getCountOfSatelliteNotificationDisplayed();
+        proto.countOfAutoExitDueToScreenOff = param.getCountOfAutoExitDueToScreenOff();
+        proto.countOfAutoExitDueToTnNetwork = param.getCountOfAutoExitDueToTnNetwork();
         mAtomsStorage.addSatelliteSessionStats(proto);
     }
 
@@ -2206,6 +2542,7 @@
         proto.datagramSizeBytes = param.getDatagramSizeBytes();
         proto.datagramTransferTimeMillis = param.getDatagramTransferTimeMillis();
         proto.isDemoMode = param.getIsDemoMode();
+        proto.carrierId = param.getCarrierId();
         mAtomsStorage.addSatelliteIncomingDatagramStats(proto);
     }
 
@@ -2218,6 +2555,7 @@
         proto.datagramSizeBytes = param.getDatagramSizeBytes();
         proto.datagramTransferTimeMillis = param.getDatagramTransferTimeMillis();
         proto.isDemoMode = param.getIsDemoMode();
+        proto.carrierId = param.getCarrierId();
         mAtomsStorage.addSatelliteOutgoingDatagramStats(proto);
     }
 
@@ -2228,6 +2566,7 @@
         proto.provisioningTimeSec = param.getProvisioningTimeSec();
         proto.isProvisionRequest = param.getIsProvisionRequest();
         proto.isCanceled = param.getIsCanceled();
+        proto.carrierId = param.getCarrierId();
         mAtomsStorage.addSatelliteProvisionStats(proto);
     }
 
@@ -2242,6 +2581,8 @@
         proto.isMultiSim = param.isMultiSim();
         proto.recommendingHandoverType = param.getRecommendingHandoverType();
         proto.isSatelliteAllowedInCurrentLocation = param.isSatelliteAllowedInCurrentLocation();
+        proto.isWifiConnected = param.isWifiConnected();
+        proto.carrierId = param.getCarrierId();
         proto.count = 1;
         mAtomsStorage.addSatelliteSosMessageRecommenderStats(proto);
     }
@@ -2280,6 +2621,8 @@
         proto.satelliteSessionGapMinSec = param.mSatelliteSessionGapMinSec;
         proto.satelliteSessionGapAvgSec = param.mSatelliteSessionGapAvgSec;
         proto.satelliteSessionGapMaxSec = param.mSatelliteSessionGapMaxSec;
+        proto.carrierId = param.getCarrierId();
+        proto.isDeviceEntitled = param.isDeviceEntitled();
         mAtomsStorage.addCarrierRoamingSatelliteControllerStats(proto);
     }
 
@@ -2317,6 +2660,8 @@
         proto.resultCode = param.getResultCode();
         proto.countryCodes = param.getCountryCodes();
         proto.configDataSource = param.getConfigDataSource();
+        proto.carrierId = param.getCarrierId();
+        proto.triggeringEvent = param.getTriggeringEvent();
         mAtomsStorage.addSatelliteAccessControllerStats(proto);
     }
 }
diff --git a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
index 911424e..b6a26c6 100644
--- a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
@@ -26,6 +26,16 @@
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_THIRTY_MINUTES;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_MORE_THAN_ONE_HOUR;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_UNKNOWN;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_DIALING;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_DISCONNECTED;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_DISCONNECTING;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_HOLDING;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_IDLE;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_INCOMING;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_WAITING;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_UNKNOWN;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MT;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_FULLBAND;
@@ -540,6 +550,7 @@
 
         // Compute time it took to fail setup (except for MT calls that have never been picked up)
         if (proto.setupFailed && proto.setupBeginMillis != 0L && proto.setupDurationMillis == 0) {
+            proto.preciseCallStateOnSetup = convertCallStateEnumToInt(Call.State.DISCONNECTED);
             proto.setupDurationMillis = (int) (getTimeMillis() - proto.setupBeginMillis);
         }
 
@@ -632,6 +643,7 @@
 
     private void checkCallSetup(Connection conn, VoiceCallSession proto) {
         if (proto.setupBeginMillis != 0L && isSetupFinished(conn.getCall())) {
+            proto.preciseCallStateOnSetup = convertCallStateEnumToInt(conn.getState());
             proto.setupDurationMillis = (int) (getTimeMillis() - proto.setupBeginMillis);
             proto.setupBeginMillis = 0L;
         }
@@ -1092,4 +1104,29 @@
                 proto.handoverInProgress = false;
         }
     }
+
+    private int convertCallStateEnumToInt(Call.State state) {
+        switch (state) {
+            case IDLE:
+                return VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_IDLE;
+            case ACTIVE:
+                return VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
+            case HOLDING:
+                return VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_HOLDING;
+            case DIALING:
+                return VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_DIALING;
+            case ALERTING:
+                return VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
+            case INCOMING:
+                return VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_INCOMING;
+            case WAITING:
+                return VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_WAITING;
+            case DISCONNECTED:
+                return VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_DISCONNECTED;
+            case DISCONNECTING:
+                return VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_DISCONNECTING;
+            default:
+                return VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_UNKNOWN;
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramController.java b/src/java/com/android/internal/telephony/satellite/DatagramController.java
index ff2ee9f..9f6edf2 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramController.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramController.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony.satellite;
 
-import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
 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;
@@ -253,6 +252,7 @@
             @NonNull Consumer<Integer> callback) {
         mDatagramDispatcher.sendSatelliteDatagram(subId, datagramType, datagram,
                 needFullScreenPointingUI, callback);
+        mPointingAppController.onSendDatagramRequested(subId, datagramType);
     }
 
     /**
@@ -282,7 +282,7 @@
             mSendDatagramTransferState = datagramTransferState;
             mSendPendingCount = sendPendingCount;
             mSendErrorCode = errorCode;
-            notifyDatagramTransferStateChangedToSessionController();
+            notifyDatagramTransferStateChangedToSessionController(mDatagramType);
             mPointingAppController.updateSendDatagramTransferState(mSendSubId, mDatagramType,
                     mSendDatagramTransferState, mSendPendingCount, mSendErrorCode);
             retryPollPendingDatagramsInDemoMode();
@@ -311,21 +311,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();
@@ -344,6 +346,12 @@
         return mReceivePendingCount;
     }
 
+
+    /** @return {@code true} if already sent an emergency datagram during a session. */
+    public boolean isEmergencyCommunicationEstablished() {
+        return mDatagramDispatcher.isEmergencyCommunicationEstablished();
+    }
+
     /**
      * This function is used by {@link SatelliteController} to notify {@link DatagramController}
      * that satellite modem state has changed.
@@ -359,6 +367,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)
@@ -559,14 +585,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);
+            }
         }
     }
 
@@ -609,7 +637,9 @@
                         }
                     }
                 };
-                pollPendingSatelliteDatagrams(DEFAULT_SUBSCRIPTION_ID, internalCallback);
+                pollPendingSatelliteDatagrams(
+                        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 2c9463f..1517064 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
@@ -16,13 +16,19 @@
 
 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_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;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
 import static com.android.internal.telephony.satellite.DatagramController.ROUNDING_UNIT;
+import static com.android.internal.telephony.SmsDispatchersController.PendingRequest;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -35,14 +41,15 @@
 import android.telephony.DropBoxManagerLoggerBackend;
 import android.telephony.PersistentLogger;
 import android.telephony.Rlog;
-import android.telephony.SubscriptionManager;
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.SmsDispatchersController;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.SatelliteStats;
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
@@ -69,6 +76,9 @@
     private static final int EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT = 5;
     private static final int EVENT_ABORT_SENDING_SATELLITE_DATAGRAMS_DONE = 6;
     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;
@@ -88,8 +98,12 @@
     private final Object mLock = new Object();
     private long mDemoTimeoutDuration = TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE;
 
+    /** {@code true} if already sent an emergency datagram during a session */
     @GuardedBy("mLock")
-    private boolean mSendingDatagramInProgress;
+    private boolean mIsEmergencyCommunicationEstablished = false;
+
+    @GuardedBy("mLock")
+    private boolean mSendingInProgress;
 
     /**
      * Map key: datagramId, value: SendSatelliteDatagramArgument to retry sending emergency
@@ -107,12 +121,28 @@
     private final LinkedHashMap<Long, SendSatelliteDatagramArgument>
             mPendingNonEmergencyDatagramsMap = new LinkedHashMap<>();
 
+    /**
+     * Map key: messageId, value: {@link PendingRequest} which contains all the information to send
+     * carrier roaming nb iot ntn SMS.
+     */
+    @GuardedBy("mLock")
+    private final LinkedHashMap<Long, PendingRequest> mPendingSmsMap = new LinkedHashMap<>();
+
     private long mWaitTimeForDatagramSendingResponse;
     private long mWaitTimeForDatagramSendingForLastMessageResponse;
     @SatelliteManager.DatagramType
     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;
+
     /**
      * Create the DatagramDispatcher singleton instance.
      * @param context The Context to use to create the DatagramDispatcher.
@@ -164,7 +194,7 @@
         }
 
         synchronized (mLock) {
-            mSendingDatagramInProgress = false;
+            mSendingInProgress = false;
         }
         mWaitTimeForDatagramSendingResponse = getWaitForDatagramSendingResponseTimeoutMillis();
         mWaitTimeForDatagramSendingForLastMessageResponse =
@@ -289,13 +319,16 @@
                     }
 
                     stopWaitForDatagramSendingResponseTimer();
-                    mSendingDatagramInProgress = false;
+                    mSendingInProgress = false;
 
                     // Log metrics about the outgoing datagram
                     reportSendDatagramCompleted(argument, error);
                     // Remove current datagram from pending map.
                     if (SatelliteServiceUtils.isSosMessage(argument.datagramType)) {
                         mPendingEmergencyDatagramsMap.remove(argument.datagramId);
+                        if (error == SATELLITE_RESULT_SUCCESS) {
+                            mIsEmergencyCommunicationEstablished = true;
+                        }
                     } else {
                         mPendingNonEmergencyDatagramsMap.remove(argument.datagramId);
                     }
@@ -304,20 +337,20 @@
                         // Update send status for current datagram
                         mDatagramController.updateSendStatus(argument.subId, argument.datagramType,
                                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS,
-                                getPendingDatagramCount(), error);
+                                getPendingMessagesCount(), error);
                         startWaitForSimulatedPollDatagramsDelayTimer(request);
                     } else {
                         // Update send status
                         mDatagramController.updateSendStatus(argument.subId, argument.datagramType,
                                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
-                                getPendingDatagramCount(), error);
+                                getPendingMessagesCount(), error);
                     }
 
-                    if (getPendingDatagramCount() > 0) {
+                    if (getPendingMessagesCount() > 0) {
                         // Send response for current datagram
                         argument.callback.accept(error);
                         // Send pending datagrams
-                        sendPendingDatagrams();
+                        sendPendingMessages();
                     } else {
                         mDatagramController.updateSendStatus(argument.subId,
                                 argument.datagramType,
@@ -341,8 +374,7 @@
             }
 
             case EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT:
-                handleEventDatagramWaitForConnectedStateTimedOut(
-                        (SendSatelliteDatagramArgument) msg.obj);
+                handleEventDatagramWaitForConnectedStateTimedOut((int) msg.obj);
                 break;
 
             case EVENT_WAIT_FOR_SIMULATED_POLL_DATAGRAMS_DELAY_TIMED_OUT:
@@ -351,6 +383,48 @@
                         (SendSatelliteDatagramArgument) request.argument);
                 break;
 
+            case CMD_SEND_SMS: {
+                PendingRequest pendingRequest = (PendingRequest) msg.obj;
+                Phone satellitePhone = SatelliteController.getInstance().getSatellitePhone();
+                if (satellitePhone == null) {
+                    ploge("CMD_SEND_SMS: satellitePhone is null.");
+                    return;
+                }
+
+                SmsDispatchersController smsDispatchersController =
+                        satellitePhone.getSmsDispatchersController();
+                if (smsDispatchersController == null) {
+                    ploge("CMD_SEND_SMS: smsDispatchersController is null.");
+                    return;
+                }
+
+                smsDispatchersController.sendCarrierRoamingNbIotNtnText(pendingRequest);
+                break;
+            }
+
+            case EVENT_SEND_SMS_DONE: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                int subId = (int) args.arg1;
+                long messageId = (long) args.arg2;
+                boolean success = (boolean) args.arg3;
+                try {
+                    handleEventSendSmsDone(subId, messageId, success);
+                } finally {
+                    args.recycle();
+                }
+                break;
+            }
+
+            case EVENT_MT_SMS_POLLING_THROTTLE_TIMED_OUT: {
+                synchronized (mLock) {
+                    mIsMtSmsPollingThrottled = false;
+                    if (mIsAligned && mModemState == SATELLITE_MODEM_STATE_CONNECTED) {
+                        sendMtSmsPollingMessage();
+                    }
+                }
+                break;
+            }
+
             default:
                 plogw("DatagramDispatcherHandler: unexpected message code: " + msg.what);
                 break;
@@ -397,28 +471,27 @@
                 plogd("sendDatagram: wait for satellite connected");
                 mDatagramController.updateSendStatus(subId, datagramType,
                         SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
-                        getPendingDatagramCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
-                startDatagramWaitForConnectedStateTimer(datagramArgs);
-            } else if (!mSendingDatagramInProgress && mDatagramController.isPollingInIdleState()) {
+                        getPendingMessagesCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
+                startDatagramWaitForConnectedStateTimer(datagramArgs.datagramType);
+            } else if (!mSendingInProgress && mDatagramController.isPollingInIdleState()) {
                 // Modem can be busy receiving datagrams, so send datagram only when modem is
                 // not busy.
-                mSendingDatagramInProgress = true;
+                mSendingInProgress = true;
                 datagramArgs.setDatagramStartTime();
                 mDatagramController.updateSendStatus(subId, datagramType,
                         SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
-                        getPendingDatagramCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
+                        getPendingMessagesCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
                 sendRequestAsync(CMD_SEND_SATELLITE_DATAGRAM, datagramArgs, phone);
             } else {
-                plogd("sendDatagram: mSendingDatagramInProgress="
-                        + mSendingDatagramInProgress + ", isPollingInIdleState="
-                        + mDatagramController.isPollingInIdleState());
+                plogd("sendDatagram: mSendingInProgress=" + mSendingInProgress
+                        + ", isPollingInIdleState=" + mDatagramController.isPollingInIdleState());
             }
         }
     }
 
     public void retrySendingDatagrams() {
         synchronized (mLock) {
-            sendPendingDatagrams();
+            sendPendingMessages();
         }
     }
 
@@ -441,6 +514,10 @@
             mIsAligned = isAligned;
             plogd("setDeviceAlignedWithSatellite: " + mIsAligned);
             if (isAligned && mIsDemoMode) handleEventSatelliteAligned();
+            if (isAligned && !mIsMtSmsPollingThrottled
+                    && mModemState == SATELLITE_MODEM_STATE_CONNECTED) {
+                sendMtSmsPollingMessage();
+            }
         }
     }
 
@@ -500,6 +577,23 @@
         removeMessages(EVENT_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_MODE_TIMED_OUT);
     }
 
+    @GuardedBy("mLock")
+    private void sendPendingMessages() {
+        plogd("sendPendingMessages");
+
+        // Pending datagrams are prioritized over pending SMS.
+        if (getPendingDatagramCount() > 0) {
+            sendPendingDatagrams();
+            return;
+        }
+
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            if (getPendingSmsCount() > 0) {
+                sendPendingSms();
+            }
+        }
+    }
+
     /**
      * Send pending satellite datagrams. Emergency datagrams are given priority over
      * non-emergency datagrams.
@@ -520,9 +614,9 @@
 
         Phone phone = SatelliteServiceUtils.getPhone();
         Set<Entry<Long, SendSatelliteDatagramArgument>> pendingDatagram = null;
-        if (!mSendingDatagramInProgress && !mPendingEmergencyDatagramsMap.isEmpty()) {
+        if (!mSendingInProgress && !mPendingEmergencyDatagramsMap.isEmpty()) {
             pendingDatagram = mPendingEmergencyDatagramsMap.entrySet();
-        } else if (!mSendingDatagramInProgress && !mPendingNonEmergencyDatagramsMap.isEmpty()) {
+        } else if (!mSendingInProgress && !mPendingNonEmergencyDatagramsMap.isEmpty()) {
             pendingDatagram = mPendingNonEmergencyDatagramsMap.entrySet();
         }
 
@@ -534,12 +628,12 @@
                 return;
             }
 
-            mSendingDatagramInProgress = true;
+            mSendingInProgress = true;
             // Sets the trigger time for getting pending datagrams
             datagramArg.setDatagramStartTime();
             mDatagramController.updateSendStatus(datagramArg.subId, datagramArg.datagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
-                    getPendingDatagramCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
+                    getPendingMessagesCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
             sendRequestAsync(CMD_SEND_SATELLITE_DATAGRAM, datagramArg, phone);
         }
     }
@@ -583,6 +677,21 @@
         plogd("abortSendingPendingDatagrams()");
         sendErrorCodeAndCleanupPendingDatagrams(mPendingEmergencyDatagramsMap, errorCode);
         sendErrorCodeAndCleanupPendingDatagrams(mPendingNonEmergencyDatagramsMap, errorCode);
+        sendErrorCodeAndCleanupPendingSms(mPendingSmsMap, errorCode);
+    }
+
+    /**
+     * Return pending datagram and SMS count
+     * @return pending messages count
+     */
+    public int getPendingMessagesCount() {
+        synchronized (mLock) {
+            if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+                return getPendingDatagramCount() + getPendingSmsCount();
+            } else {
+                return getPendingDatagramCount();
+            }
+        }
     }
 
     /**
@@ -595,6 +704,16 @@
         }
     }
 
+    /**
+     * Return pending SMS count
+     * @return pending SMS count
+     */
+    public int getPendingSmsCount() {
+        synchronized (mLock) {
+            return mPendingSmsMap.size();
+        }
+    }
+
     /** Return pending user messages count */
     public int getPendingUserMessagesCount() {
         synchronized (mLock) {
@@ -637,6 +756,7 @@
                         .setDatagramTransferTimeMillis(argument.datagramStartTime > 0
                                 ? (System.currentTimeMillis() - argument.datagramStartTime) : 0)
                         .setIsDemoMode(mIsDemoMode)
+                        .setCarrierId(SatelliteController.getInstance().getSatelliteCarrierId())
                         .build());
         if (resultCode == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
             mControllerMetricsStats.reportOutgoingDatagramSuccessCount(argument.datagramType,
@@ -666,18 +786,31 @@
      */
     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");
                 cleanUpResources();
             } else if (state == SatelliteManager.SATELLITE_MODEM_STATE_IDLE) {
-                sendPendingDatagrams();
+                sendPendingMessages();
             }
 
-            if (state == SATELLITE_MODEM_STATE_CONNECTED
-                    && isDatagramWaitForConnectedStateTimerStarted()) {
-                stopDatagramWaitForConnectedStateTimer();
-                sendPendingDatagrams();
+            if (state == SATELLITE_MODEM_STATE_CONNECTED) {
+                mHasEnteredConnectedState = true;
+                if (isDatagramWaitForConnectedStateTimerStarted()) {
+                    stopDatagramWaitForConnectedStateTimer();
+                    sendPendingMessages();
+                }
+                if (mIsAligned && !mIsMtSmsPollingThrottled) {
+                    sendMtSmsPollingMessage();
+                }
+            }
+
+            if (state == SATELLITE_MODEM_STATE_NOT_CONNECTED) {
+                if (mHasEnteredConnectedState) {
+                    mHasEnteredConnectedState = false;
+                    mShouldPollMtSms = shouldPollMtSms();
+                }
             }
         }
     }
@@ -685,18 +818,21 @@
     @GuardedBy("mLock")
     private void cleanUpResources() {
         plogd("cleanUpResources");
-        mSendingDatagramInProgress = false;
-        if (getPendingDatagramCount() > 0) {
-            mDatagramController.updateSendStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+        mSendingInProgress = false;
+        mIsEmergencyCommunicationEstablished = false;
+
+        int subId = SatelliteController.getInstance().getSelectedSatelliteSubId();
+        if (getPendingMessagesCount() > 0) {
+            mDatagramController.updateSendStatus(subId,
                     mLastSendRequestDatagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
-                    getPendingDatagramCount(), SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
+                    getPendingMessagesCount(), SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
         }
-        mDatagramController.updateSendStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+        mDatagramController.updateSendStatus(subId,
                 mLastSendRequestDatagramType,
                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
                 0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
-        abortSendingPendingDatagrams(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+        abortSendingPendingDatagrams(subId,
                 SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
 
         stopSatelliteAlignedTimer();
@@ -707,18 +843,29 @@
         mSendSatelliteDatagramRequest = null;
         mIsAligned = false;
         mLastSendRequestDatagramType = DATAGRAM_TYPE_UNKNOWN;
+        mModemState = SATELLITE_MODEM_STATE_UNKNOWN;
+        mHasEnteredConnectedState = false;
+        mShouldPollMtSms = false;
+        stopMtSmsPollingThrottle();
+    }
+
+    /** @return {@code true} if already sent an emergency datagram during a session. */
+    public boolean isEmergencyCommunicationEstablished() {
+        synchronized (mLock) {
+            return mIsEmergencyCommunicationEstablished;
+        }
     }
 
     private void startDatagramWaitForConnectedStateTimer(
-            @NonNull SendSatelliteDatagramArgument datagramArgs) {
+            @SatelliteManager.DatagramType int datagramType) {
         if (isDatagramWaitForConnectedStateTimerStarted()) {
             plogd("DatagramWaitForConnectedStateTimer is already started");
             return;
         }
         sendMessageDelayed(obtainMessage(
-                        EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT, datagramArgs),
+                        EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT, datagramType),
                 mDatagramController.getDatagramWaitTimeForConnectedState(
-                        SatelliteServiceUtils.isLastSosMessage(datagramArgs.datagramType)));
+                        SatelliteServiceUtils.isLastSosMessage(datagramType)));
     }
 
     private void stopDatagramWaitForConnectedStateTimer() {
@@ -761,20 +908,21 @@
     }
 
     private void handleEventDatagramWaitForConnectedStateTimedOut(
-            @NonNull SendSatelliteDatagramArgument argument) {
+            @SatelliteManager.DatagramType int datagramType) {
         plogw("Timed out to wait for satellite connected before sending datagrams");
         synchronized (mLock) {
+            int subId = SatelliteController.getInstance().getSelectedSatelliteSubId();
             // Update send status
-            mDatagramController.updateSendStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                    argument.datagramType,
+            mDatagramController.updateSendStatus(subId,
+                    datagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
-                    getPendingDatagramCount(),
+                    getPendingMessagesCount(),
                     SATELLITE_RESULT_NOT_REACHABLE);
-            mDatagramController.updateSendStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                    argument.datagramType,
+            mDatagramController.updateSendStatus(subId,
+                    datagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
                     0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
-            abortSendingPendingDatagrams(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+            abortSendingPendingDatagrams(subId,
                     SATELLITE_RESULT_NOT_REACHABLE);
         }
     }
@@ -827,12 +975,12 @@
             // Ask vendor service to abort all datagram-sending requests
             SatelliteModemInterface.getInstance().abortSendingSatelliteDatagrams(
                     obtainMessage(EVENT_ABORT_SENDING_SATELLITE_DATAGRAMS_DONE, argument));
-            mSendingDatagramInProgress = false;
+            mSendingInProgress = false;
 
             // Update send status
             mDatagramController.updateSendStatus(argument.subId, argument.datagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
-                    getPendingDatagramCount(), SATELLITE_RESULT_MODEM_TIMEOUT);
+                    getPendingMessagesCount(), SATELLITE_RESULT_MODEM_TIMEOUT);
             mDatagramController.updateSendStatus(argument.subId, argument.datagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
                     0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
@@ -934,6 +1082,232 @@
         plogd("setTimeoutDatagramDelayInDemoMode " + mDemoTimeoutDuration + " reset=" + reset);
     }
 
+    /**
+     * Send carrier roaming nb iot ntn sms.
+     *
+     * Store SMS in a pending list until following conditions are met:
+     * - If messages can be sent only when satellite is connected, then wait until modem state
+     * becomes {@link SatelliteManager#SATELLITE_MODEM_STATE_CONNECTED}
+     * - If modem is already sending datagrms/SMS or receiving datagrams, then wait until modem
+     * becomes IDLE to send current SMS.
+     *
+     * @param pendingSms {@link PendingRequest} that contains all the information required to send
+     *                    carrier roaming nb iot ntn SMS.
+     */
+    public void sendSms(@NonNull PendingRequest pendingSms) {
+        SatelliteController.getInstance().startPointingUI();
+
+        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 = pendingSms.isMtSmsPolling ?
+                    DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS : DATAGRAM_TYPE_SMS;
+            mLastSendRequestDatagramType = datagramType;
+
+            if (mDatagramController.needsWaitingForSatelliteConnected(datagramType)) {
+                plogd("sendSms: wait for satellite connected");
+                mDatagramController.updateSendStatus(subId, datagramType,
+                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
+                        getPendingMessagesCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
+                startDatagramWaitForConnectedStateTimer(datagramType);
+            } else if (!mSendingInProgress && mDatagramController.isPollingInIdleState()) {
+                mSendingInProgress = true;
+                mDatagramController.updateSendStatus(subId, datagramType,
+                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+                        getPendingMessagesCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
+
+                sendMessage(obtainMessage(CMD_SEND_SMS, pendingSms));
+            } else {
+                plogd("sendSms: mSendingInProgress=" + mSendingInProgress
+                        + ", isPollingInIdleState=" + mDatagramController.isPollingInIdleState());
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void sendPendingSms() {
+        plogd("sendPendingSms");
+        if (!mDatagramController.isPollingInIdleState()) {
+            // Datagram or SMS should be sent to satellite modem when modem is free.
+            plogd("sendPendingSms: modem is receiving datagrams");
+            return;
+        }
+
+        int subId = SatelliteController.getInstance().getSelectedSatelliteSubId();
+        Set<Entry<Long, PendingRequest>> pendingSms = null;
+        if (!mSendingInProgress) {
+            pendingSms = mPendingSmsMap.entrySet();
+        }
+
+        if (pendingSms != null && pendingSms.iterator().hasNext()) {
+            if (mDatagramController.needsWaitingForSatelliteConnected(DATAGRAM_TYPE_SMS)) {
+                plogd("sendPendingSms: wait for satellite connected");
+                return;
+            }
+
+            mSendingInProgress = true;
+            PendingRequest pendingRequest = pendingSms.iterator().next().getValue();
+            mDatagramController.updateSendStatus(subId,
+                    pendingRequest.isMtSmsPolling ?
+                            DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS : DATAGRAM_TYPE_SMS,
+                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
+                    getPendingMessagesCount(), SATELLITE_RESULT_SUCCESS);
+            sendMessage(obtainMessage(CMD_SEND_SMS, pendingRequest));
+        } else {
+            plogd("sendPendingSms: mSendingInProgress=" + mSendingInProgress
+                    + " pendingSmsCount=" + getPendingSmsCount());
+        }
+    }
+
+    /**
+     * Sending MO SMS is completed.
+     * @param subId subscription ID
+     * @param messageId message ID of MO SMS
+     * @param success boolean specifying whether MO SMS is successfully sent or not.
+     */
+    public void onSendSmsDone(int subId, long messageId, boolean success) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = subId;
+        args.arg2 = messageId;
+        args.arg3 = success;
+        sendMessage(obtainMessage(EVENT_SEND_SMS_DONE, args));
+    }
+
+    @GuardedBy("mLock")
+    private void sendErrorCodeAndCleanupPendingSms(
+            LinkedHashMap<Long, PendingRequest> pendingSmsMap,
+            @SatelliteManager.SatelliteResult int errorCode) {
+        if (pendingSmsMap.size() == 0) {
+            plogd("sendErrorCodeAndCleanupPendingSms: pendingSmsMap is empty.");
+            return;
+        }
+        ploge("sendErrorCodeAndCleanupPendingSms: cleaning up resources. "
+                + "pendingSmsMap size=" + getPendingSmsCount());
+
+        Phone satellitePhone = SatelliteController.getInstance().getSatellitePhone();
+        if (satellitePhone == null) {
+            ploge("sendErrorCodeAndCleanupPendingSms: satellitePhone is null.");
+            pendingSmsMap.clear();
+            return;
+        }
+
+        SmsDispatchersController smsDispatchersController =
+                satellitePhone.getSmsDispatchersController();
+        if (smsDispatchersController == null) {
+            ploge("sendErrorCodeAndCleanupPendingSms: smsDispatchersController is null.");
+            pendingSmsMap.clear();
+            return;
+        }
+
+        // Send error code to all the pending text
+        for (Entry<Long, PendingRequest> entry : pendingSmsMap.entrySet()) {
+            PendingRequest pendingRequest = entry.getValue();
+            smsDispatchersController.onSendCarrierRoamingNbIotNtnTextError(
+                    pendingRequest, errorCode);
+        }
+
+        // Clear pending text map
+        pendingSmsMap.clear();
+    }
+
+    private void handleEventSendSmsDone(int subId, long messageId, boolean success) {
+        synchronized (mLock) {
+            mSendingInProgress = false;
+            PendingRequest pendingSms = mPendingSmsMap.remove(messageId);
+            int datagramType = pendingSms != null && pendingSms.isMtSmsPolling
+                    ? DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS  : DATAGRAM_TYPE_SMS;
+
+            plogd("handleEventSendSmsDone subId=" + subId + " messageId=" + messageId
+                    + " success=" + success);
+            if (success) {
+                // Update send status for current datagram
+                mDatagramController.updateSendStatus(subId, datagramType,
+                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS,
+                        getPendingMessagesCount(), SATELLITE_RESULT_SUCCESS);
+                if (datagramType == DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS) {
+                    startMtSmsPollingThrottle();
+                }
+            } else {
+                mDatagramController.updateSendStatus(subId, datagramType,
+                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
+                        getPendingMessagesCount(), SATELLITE_RESULT_NETWORK_ERROR);
+            }
+
+            if (getPendingMessagesCount() > 0) {
+                sendPendingMessages();
+            } else {
+                mDatagramController.updateSendStatus(subId, datagramType,
+                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, 0,
+                        SatelliteManager.SATELLITE_RESULT_SUCCESS);
+            }
+        }
+    }
+
+    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() {
+        return isEnabledMtSmsPolling()
+                && SatelliteController.getInstance().isInCarrierRoamingNbIotNtn();
+    }
+
+    @GuardedBy("mLock")
+    private void sendMtSmsPollingMessage() {
+        if (!mShouldPollMtSms) {
+            return;
+        }
+
+        plogd("sendMtSmsPollingMessage");
+        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);
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
index ea75f03..a921b89 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);
 
@@ -376,6 +378,7 @@
 
                     if (pendingCount <= 0) {
                         sInstance.mDatagramController.updateReceiveStatus(mSubId,
+                                SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
                                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
                                 pendingCount, SatelliteManager.SATELLITE_RESULT_SUCCESS);
                     } else {
@@ -455,8 +458,7 @@
 
                 if (mIsDemoMode && error == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
                     SatelliteDatagram datagram = mDatagramController.popDemoModeDatagram();
-                    final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(
-                            request.subId, mContext);
+                    final int validSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
                     SatelliteDatagramListenerHandler listenerHandler =
                             mSatelliteDatagramListenerHandlers.get(validSubId);
                     if (listenerHandler != null) {
@@ -474,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);
@@ -518,7 +522,7 @@
             return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
         }
 
-        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
+        final int validSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
         SatelliteDatagramListenerHandler satelliteDatagramListenerHandler =
                 mSatelliteDatagramListenerHandlers.get(validSubId);
         if (satelliteDatagramListenerHandler == null) {
@@ -544,7 +548,7 @@
      */
     public void unregisterForSatelliteDatagram(int subId,
             @NonNull ISatelliteDatagramCallback callback) {
-        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
+        final int validSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
         SatelliteDatagramListenerHandler handler =
                 mSatelliteDatagramListenerHandlers.get(validSubId);
         if (handler != null) {
@@ -576,7 +580,8 @@
             callback.accept(SatelliteManager.SATELLITE_RESULT_MODEM_BUSY);
             return;
         }
-        pollPendingSatelliteDatagramsInternal(subId, callback);
+        pollPendingSatelliteDatagramsInternal(
+                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, callback);
     }
 
     private void handleSatelliteConnectedEvent() {
@@ -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);
@@ -695,13 +702,17 @@
             }
             stopDatagramWaitForConnectedStateTimer();
         }
+
+        int subId = SatelliteController.getInstance().getSelectedSatelliteSubId();
         if (mDatagramController.isReceivingDatagrams()) {
-            mDatagramController.updateReceiveStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+            mDatagramController.updateReceiveStatus(subId,
+                    SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
                     mDatagramController.getReceivePendingCount(),
                     SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
         }
-        mDatagramController.updateReceiveStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+        mDatagramController.updateReceiveStatus(subId,
+                SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, 0,
                 SatelliteManager.SATELLITE_RESULT_SUCCESS);
         cleanupDemoModeResources();
@@ -746,6 +757,7 @@
                         .setDatagramSizeBytes(datagramSizeRoundedBytes)
                         .setDatagramTransferTimeMillis(datagramTransferTime)
                         .setIsDemoMode(mIsDemoMode)
+                        .setCarrierId(SatelliteController.getInstance().getSatelliteCarrierId())
                         .build());
 
         mControllerMetricsStats.reportIncomingDatagramCount(resultCode, mIsDemoMode);
@@ -854,11 +866,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 3c31ae8..d908597 100644
--- a/src/java/com/android/internal/telephony/satellite/DemoSimulator.java
+++ b/src/java/com/android/internal/telephony/satellite/DemoSimulator.java
@@ -131,7 +131,7 @@
                 NtnSignalStrength ntnSignalStrength = new NtnSignalStrength();
                 ntnSignalStrength.signalStrengthLevel = 0;
                 mISatelliteListener.onSatelliteModemStateChanged(
-                        SatelliteModemState.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+                        SatelliteModemState.SATELLITE_MODEM_STATE_OUT_OF_SERVICE);
                 mISatelliteListener.onNtnSignalStrengthChanged(ntnSignalStrength);
 
                 synchronized (mLock) {
@@ -191,7 +191,7 @@
                 NtnSignalStrength ntnSignalStrength = new NtnSignalStrength();
                 ntnSignalStrength.signalStrengthLevel = 2;
                 mISatelliteListener.onSatelliteModemStateChanged(
-                        SatelliteModemState.SATELLITE_MODEM_STATE_CONNECTED);
+                        SatelliteModemState.SATELLITE_MODEM_STATE_IN_SERVICE);
                 mISatelliteListener.onNtnSignalStrengthChanged(ntnSignalStrength);
 
                 synchronized (mLock) {
@@ -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/PointingAppController.java b/src/java/com/android/internal/telephony/satellite/PointingAppController.java
index 06281c7..9606150 100644
--- a/src/java/com/android/internal/telephony/satellite/PointingAppController.java
+++ b/src/java/com/android/internal/telephony/satellite/PointingAppController.java
@@ -34,9 +34,11 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.telephony.DropBoxManagerLoggerBackend;
 import android.telephony.PersistentLogger;
 import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.PointingInfo;
 import android.telephony.satellite.SatelliteManager;
@@ -189,6 +191,7 @@
         public static final int EVENT_SEND_DATAGRAM_STATE_CHANGED = 2;
         public static final int EVENT_RECEIVE_DATAGRAM_STATE_CHANGED = 3;
         public static final int EVENT_DATAGRAM_TRANSFER_STATE_CHANGED = 4;
+        public static final int EVENT_SEND_DATAGRAM_REQUESTED = 5;
 
         private final ConcurrentHashMap<IBinder, ISatelliteTransmissionUpdateCallback> mListeners;
 
@@ -277,6 +280,24 @@
                     break;
                 }
 
+                case EVENT_SEND_DATAGRAM_REQUESTED: {
+                    logd("Received EVENT_SEND_DATAGRAM_REQUESTED");
+                    int datagramType = (int) msg.obj;
+                    List<IBinder> toBeRemoved = new ArrayList<>();
+                    mListeners.values().forEach(listener -> {
+                        try {
+                            listener.onSendDatagramRequested(datagramType);
+                        } catch (RemoteException e) {
+                            logd("EVENT_SEND_DATAGRAM_REQUESTED RemoteException: " + e);
+                            toBeRemoved.add(listener.asBinder());
+                        }
+                    });
+                    toBeRemoved.forEach(listener -> {
+                        mListeners.remove(listener);
+                    });
+                    break;
+                }
+
                 default:
                     loge("SatelliteTransmissionUpdateHandler unknown event: " + msg.what);
             }
@@ -290,6 +311,7 @@
      */
     public void registerForSatelliteTransmissionUpdates(int subId,
             ISatelliteTransmissionUpdateCallback callback) {
+        subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
         SatelliteTransmissionUpdateHandler handler =
                 mSatelliteTransmissionUpdateHandlers.get(subId);
         if (handler != null) {
@@ -318,6 +340,7 @@
      */
     public void unregisterForSatelliteTransmissionUpdates(int subId, Consumer<Integer> result,
             ISatelliteTransmissionUpdateCallback callback) {
+        subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
         SatelliteTransmissionUpdateHandler handler =
                 mSatelliteTransmissionUpdateHandlers.get(subId);
         if (handler != null) {
@@ -404,7 +427,7 @@
             mLastNeedFullScreenPointingUI = needFullScreenPointingUI;
             mLastIsDemoMode = isDemoMode;
             mLastIsEmergency = isEmergency;
-            mContext.startActivity(launchIntent);
+            mContext.startActivityAsUser(launchIntent, UserHandle.CURRENT);
         } catch (ActivityNotFoundException ex) {
             ploge("startPointingUI: Pointing UI app activity is not found, ex=" + ex);
         }
@@ -426,6 +449,7 @@
             int sendPendingCount, int errorCode) {
         DatagramTransferStateHandlerRequest request = new DatagramTransferStateHandlerRequest(
                 datagramType, datagramTransferState, sendPendingCount, errorCode);
+        subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
         SatelliteTransmissionUpdateHandler handler =
                 mSatelliteTransmissionUpdateHandlers.get(subId);
 
@@ -439,12 +463,32 @@
         }
     }
 
+    /**
+     * This API is used to notify PointingAppController that a send datagram has just been
+     * requested.
+     */
+    public void onSendDatagramRequested(
+            int subId, @SatelliteManager.DatagramType int datagramType) {
+        subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+        SatelliteTransmissionUpdateHandler handler =
+                mSatelliteTransmissionUpdateHandlers.get(subId);
+        if (handler != null) {
+            Message msg = handler.obtainMessage(
+                    SatelliteTransmissionUpdateHandler.EVENT_SEND_DATAGRAM_REQUESTED,
+                    datagramType);
+            msg.sendToTarget();
+        } else {
+            ploge("SatelliteTransmissionUpdateHandler not found for subId: " + subId);
+        }
+    }
+
     public void updateReceiveDatagramTransferState(int subId,
             @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState,
             int receivePendingCount, int errorCode) {
         DatagramTransferStateHandlerRequest request = new DatagramTransferStateHandlerRequest(
                 SatelliteManager.DATAGRAM_TYPE_UNKNOWN, datagramTransferState, receivePendingCount,
                 errorCode);
+        subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
         SatelliteTransmissionUpdateHandler handler =
                 mSatelliteTransmissionUpdateHandlers.get(subId);
 
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java b/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java
index 384dfa5..a5afe4a 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java
@@ -104,4 +104,22 @@
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AccessControlType {}
+
+    //// Unknown reason.
+    public static final int TRIGGERING_EVENT_UNKNOWN = 0;
+    // Satellite Access Controller has been triggered by an external event.
+    public static final int TRIGGERING_EVENT_EXTERNAL_REQUEST = 1;
+    // Satellite Access Controller has been triggered by an MCC change event.
+    public static final int TRIGGERING_EVENT_MCC_CHANGED = 2;
+    //Satellite Access Controller has been triggered due to the location setting being enabled.
+    public static final int TRIGGERING_EVENT_LOCATION_SETTINGS_ENABLED = 3;
+
+    @IntDef(prefix = {"TRIGGERING_EVENT_"}, value = {
+            TRIGGERING_EVENT_UNKNOWN,
+            TRIGGERING_EVENT_EXTERNAL_REQUEST,
+            TRIGGERING_EVENT_MCC_CHANGED,
+            TRIGGERING_EVENT_LOCATION_SETTINGS_ENABLED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TriggeringEvent {}
 }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 14e7b76..f39e9c3 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -16,24 +16,44 @@
 
 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_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;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT;
 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_SATELLITE_ATTACH_SUPPORTED_BOOL;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT;
 import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL;
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_ESOS_SUPPORTED_BOOL;
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE;
+import static android.telephony.CarrierConfigManager.KEY_SATELLITE_NIDD_APN_NAME_STRING;
+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_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.SubscriptionManager.SATELLITE_ATTACH_ENABLED_FOR_CARRIER;
 import static android.telephony.SubscriptionManager.SATELLITE_ENTITLEMENT_STATUS;
 import static android.telephony.SubscriptionManager.isValidSubscriptionId;
+import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE;
 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.KEY_NTN_SIGNAL_STRENGTH;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_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_REQUEST_ABORTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
@@ -46,16 +66,21 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.StatusBarManager;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 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.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;
@@ -71,6 +96,7 @@
 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;
@@ -82,6 +108,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Telephony;
+import android.telecom.TelecomManager;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
 import android.telephony.DropBoxManagerLoggerBackend;
@@ -89,6 +116,7 @@
 import android.telephony.PersistentLogger;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.satellite.INtnSignalStrengthCallback;
@@ -102,6 +130,11 @@
 import android.telephony.satellite.SatelliteCapabilities;
 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.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -116,6 +149,7 @@
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyCountryDetector;
 import com.android.internal.telephony.configupdate.ConfigParser;
 import com.android.internal.telephony.configupdate.ConfigProviderAdaptor;
 import com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver;
@@ -125,6 +159,7 @@
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
 import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats;
 import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.FunctionalUtils;
@@ -138,6 +173,7 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
@@ -172,7 +208,8 @@
     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";
@@ -180,6 +217,10 @@
     public static final long DEFAULT_CARRIER_EMERGENCY_CALL_WAIT_FOR_CONNECTION_TIMEOUT_MILLIS =
             TimeUnit.SECONDS.toMillis(30);
 
+    /** Sets report entitled metrics cool down to 23 hours to help enforcing privacy requirement.*/
+    private static final long WAIT_FOR_REPORT_ENTITLED_MERTICS_TIMEOUT_MILLIS =
+            TimeUnit.HOURS.toMillis(23);
+
     /** 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;
@@ -203,7 +244,6 @@
     private static final int EVENT_RADIO_STATE_CHANGED = 23;
     private static final int CMD_IS_SATELLITE_PROVISIONED = 24;
     private static final int EVENT_IS_SATELLITE_PROVISIONED_DONE = 25;
-    private static final int EVENT_SATELLITE_PROVISION_STATE_CHANGED = 26;
     private static final int EVENT_PENDING_DATAGRAMS = 27;
     private static final int EVENT_SATELLITE_MODEM_STATE_CHANGED = 28;
     private static final int EVENT_SET_SATELLITE_PLMN_INFO_DONE = 29;
@@ -216,10 +256,24 @@
     private static final int EVENT_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING_DONE = 36;
     private static final int EVENT_SERVICE_STATE_CHANGED = 37;
     private static final int EVENT_SATELLITE_CAPABILITIES_CHANGED = 38;
-    private static final int EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT = 39;
+    protected static final int EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT = 39;
     private static final int EVENT_SATELLITE_CONFIG_DATA_UPDATED = 40;
     private static final int EVENT_SATELLITE_SUPPORTED_STATE_CHANGED = 41;
     private static final int EVENT_NOTIFY_NTN_HYSTERESIS_TIMED_OUT = 42;
+    private static final int CMD_EVALUATE_ESOS_PROFILES_PRIORITIZATION = 43;
+    private static final int CMD_UPDATE_PROVISION_SATELLITE_TOKEN = 44;
+    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;
+    protected static final int
+            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;
 
     @NonNull private static SatelliteController sInstance;
     @NonNull private final Context mContext;
@@ -233,9 +287,16 @@
     @NonNull private SessionMetricsStats mSessionMetricsStats;
     @NonNull private CarrierRoamingSatelliteControllerStats mCarrierRoamingSatelliteControllerStats;
     @NonNull private final SubscriptionManagerService mSubscriptionManagerService;
+    @NonNull private final TelephonyCountryDetector mCountryDetector;
+    @NonNull private final TelecomManager mTelecomManager;
     private final CommandsInterface mCi;
     private ContentResolver mContentResolver;
     private final DeviceStateMonitor mDSM;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected final Object mSatellitePhoneLock = new Object();
+    @GuardedBy("mSatellitePhoneLock")
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected Phone mSatellitePhone = null;
 
     private final Object mRadioStateLock = new Object();
 
@@ -256,19 +317,30 @@
     private boolean mDisableWifiOnSatelliteEnabled = false;
 
     private final Object mSatelliteEnabledRequestLock = new Object();
+    /* This variable is used to store the first enable request that framework has received in the
+     * current session.
+     */
     @GuardedBy("mSatelliteEnabledRequestLock")
     private RequestSatelliteEnabledArgument mSatelliteEnabledRequest = null;
+    /* This variable is used to store a disable request that framework has received.
+     */
+    @GuardedBy("mSatelliteEnabledRequestLock")
+    private RequestSatelliteEnabledArgument mSatelliteDisabledRequest = null;
+    /* This variable is used to store an enable request that updates the enable attributes of an
+     * existing satellite session.
+     */
+    @GuardedBy("mSatelliteEnabledRequestLock")
+    private RequestSatelliteEnabledArgument mSatelliteEnableAttributesUpdateRequest = null;
     /** Flag to indicate that satellite is enabled successfully
      * and waiting for all the radios to be disabled so that success can be sent to callback
      */
     @GuardedBy("mSatelliteEnabledRequestLock")
     private boolean mWaitingForRadioDisabled = false;
-
+    @GuardedBy("mSatelliteEnabledRequestLock")
     private boolean mWaitingForDisableSatelliteModemResponse = false;
+    @GuardedBy("mSatelliteEnabledRequestLock")
     private boolean mWaitingForSatelliteModemOff = false;
 
-    private final AtomicBoolean mRegisteredForProvisionStateChangedWithSatelliteService =
-            new AtomicBoolean(false);
     private final AtomicBoolean mRegisteredForPendingDatagramCountWithSatelliteService =
             new AtomicBoolean(false);
     private final AtomicBoolean mRegisteredForSatelliteModemStateChangedWithSatelliteService =
@@ -282,6 +354,10 @@
             new AtomicBoolean(false);
     private final AtomicBoolean mRegisteredForSatelliteSupportedStateChanged =
             new AtomicBoolean(false);
+    private final AtomicBoolean mRegisteredForSatelliteRegistrationFailure =
+            new AtomicBoolean(false);
+    private final AtomicBoolean mRegisteredForTerrestrialNetworkAvailableChanged =
+            new AtomicBoolean(false);
     /**
      * Map key: subId, value: callback to get error code of the provision request.
      */
@@ -310,6 +386,18 @@
      */
     private final ConcurrentHashMap<IBinder, ISatelliteSupportedStateCallback>
             mSatelliteSupportedStateChangedListeners = new ConcurrentHashMap<>();
+
+    /**
+     * Map key: binder of the callback, value: callback to satellite registration failure
+     */
+    private final ConcurrentHashMap<IBinder, ISatelliteModemStateCallback>
+            mSatelliteRegistrationFailureListeners = new ConcurrentHashMap<>();
+    /**
+     * Map key: binder of the callback, value: callback to receive terrestrial network
+     * available changed
+     */
+    private final ConcurrentHashMap<IBinder, ISatelliteModemStateCallback>
+            mTerrestrialNetworkAvailableChangedListeners = new ConcurrentHashMap<>();
     private final Object mIsSatelliteSupportedLock = new Object();
     @GuardedBy("mIsSatelliteSupportedLock")
     private Boolean mIsSatelliteSupported = null;
@@ -318,13 +406,19 @@
     private final Object mIsSatelliteEnabledLock = new Object();
     @GuardedBy("mIsSatelliteEnabledLock")
     private Boolean mIsSatelliteEnabled = null;
-    private final Object mIsRadioOnLock = new Object();
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected final Object mIsRadioOnLock = new Object();
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected boolean mIsRadioOn;
     @GuardedBy("mIsRadioOnLock")
-    private boolean mIsRadioOn = false;
-    private final Object mSatelliteViaOemProvisionLock = new Object();
-    @GuardedBy("mSatelliteViaOemProvisionLock")
-    private Boolean mIsSatelliteViaOemProvisioned = null;
-    @GuardedBy("mSatelliteViaOemProvisionLock")
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected boolean mRadioOffRequested = false;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected final Object mDeviceProvisionLock = new Object();
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @GuardedBy("mDeviceProvisionLock")
+    protected Boolean mIsDeviceProvisioned = null;
+    @GuardedBy("mDeviceProvisionLock")
     private Boolean mOverriddenIsSatelliteViaOemProvisioned = null;
     private final Object mSatelliteCapabilitiesLock = new Object();
     @GuardedBy("mSatelliteCapabilitiesLock")
@@ -349,6 +443,8 @@
             mCarrierConfigChangeListener;
     @NonNull private final ConfigProviderAdaptor.Callback mConfigDataUpdatedCallback;
     @NonNull private final Object mCarrierConfigArrayLock = new Object();
+    @NonNull
+    private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener;
     @GuardedBy("mCarrierConfigArrayLock")
     @NonNull private final SparseArray<PersistableBundle> mCarrierConfigArray = new SparseArray<>();
     @GuardedBy("mIsSatelliteEnabledLock")
@@ -380,6 +476,17 @@
     @GuardedBy("mSatelliteConnectedLock")
     @NonNull private final SparseBooleanArray mInitialized = new SparseBooleanArray();
 
+    /**
+     * Boolean set to {@code true} when device is eligible to connect to carrier roaming
+     * non-terrestrial network else set to {@code false}.
+     */
+    @GuardedBy("mSatellitePhoneLock")
+    private Boolean mLastNotifiedNtnEligibility = null;
+    @GuardedBy("mSatellitePhoneLock")
+    private boolean mNtnEligibilityHysteresisTimedOut = false;
+    @GuardedBy("mSatellitePhoneLock")
+    private boolean mCheckingAccessRestrictionInProgress = false;
+
     @GuardedBy("mSatelliteConnectedLock")
     @NonNull private final Map<Integer, CarrierRoamingSatelliteSessionStats>
             mCarrierRoamingSatelliteSessionStatsMap = new HashMap<>();
@@ -422,9 +529,29 @@
     @GuardedBy("mSupportedSatelliteServicesLock")
     private final SparseArray<List<String>> mMergedPlmnListPerCarrier = new SparseArray<>();
     private static AtomicLong sNextSatelliteEnableRequestId = new AtomicLong(0);
+    // key : subscriberId, value : provisioned or not.
+    @GuardedBy("mSatelliteTokenProvisionedLock")
+    private Map<String, Boolean> mProvisionedSubscriberId = new HashMap<>();
+    // key : subscriberId, value : subId
+    @GuardedBy("mSatelliteTokenProvisionedLock")
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected Map<String, Integer> mSubscriberIdPerSub = new HashMap<>();
+    // key : priority, low value is high, value : List<SubscriptionInfo>
+    @GuardedBy("mSatelliteTokenProvisionedLock")
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected TreeMap<Integer, List<SubscriptionInfo>> mSubsInfoListPerPriority = new TreeMap<>();
+    // The ID of the satellite subscription that has highest priority and is provisioned.
+    @GuardedBy("mSatelliteTokenProvisionedLock")
+    private int mSelectedSatelliteSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    // The last ICC ID that framework configured to modem.
+    @GuardedBy("mSatelliteTokenProvisionedLock")
+    private String mLastConfiguredIccId;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull protected final Object mSatelliteTokenProvisionedLock = new Object();
     private long mWaitTimeForSatelliteEnablingResponse;
     private long mDemoPointingAlignedDurationMillis;
     private long mDemoPointingNotAlignedDurationMillis;
+    private long mEvaluateEsosProfilesPrioritizationDurationMillis;
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private long mLastEmergencyCallTime;
@@ -450,6 +577,68 @@
 
     // Variable for backup and restore device's screen rotation settings.
     private String mDeviceRotationLockToBackupAndRestore = null;
+    // This is used for testing only. Context#getSystemService is a final API and cannot be
+    // mocked. Using this to inject a mock SubscriptionManager to work around this limitation.
+    private SubscriptionManager mInjectSubscriptionManager = null;
+
+    private final Object mIsWifiConnectedLock = new Object();
+    @GuardedBy("mIsWifiConnectedLock")
+    private boolean mIsWifiConnected = false;
+    private boolean mHasSentBroadcast = false;
+    // For satellite CTS test which to configure intent component with the necessary values.
+    private boolean mChangeIntentComponent = false;
+    private String mConfigSatelliteGatewayServicePackage = "";
+    private String mConfigSatelliteCarrierRoamingEsosProvisionedClass = "";
+
+    private boolean mIsNotificationShowing = false;
+    private static final String OPEN_MESSAGE_BUTTON = "open_message_button";
+    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 BroadcastReceiver
+            mDefaultSmsSubscriptionChangedBroadcastReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (intent.getAction().equals(
+                            SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED)) {
+                        plogd("Default SMS subscription changed");
+                        sendRequestAsync(CMD_EVALUATE_ESOS_PROFILES_PRIORITIZATION, null, null);
+                    }
+                }
+            };
+
+    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();
 
     /**
      * @return The singleton instance of SatelliteController.
@@ -495,12 +684,19 @@
         mContext = context;
         mFeatureFlags = featureFlags;
         Phone phone = SatelliteServiceUtils.getPhone();
+        synchronized (mSatellitePhoneLock) {
+            mSatellitePhone = phone;
+        }
         mCi = phone.mCi;
         mDSM = phone.getDeviceStateMonitor();
         // Create the SatelliteModemInterface singleton, which is used to manage connections
         // to the satellite service and HAL interface.
         mSatelliteModemInterface = SatelliteModemInterface.make(
                 mContext, this, mFeatureFlags);
+        mCountryDetector = TelephonyCountryDetector.getInstance(context, mFeatureFlags);
+        mCountryDetector.registerForWifiConnectivityStateChanged(this,
+                EVENT_WIFI_CONNECTIVITY_STATE_CHANGED, null);
+        mTelecomManager = mContext.getSystemService(TelecomManager.class);
 
         // Create the PointingUIController singleton,
         // which is used to manage interactions with PointingUI app.
@@ -525,7 +721,6 @@
             mIsRadioOn = phone.isRadioOn();
         }
 
-        registerForSatelliteProvisionStateChanged();
         registerForPendingDatagramCount();
         registerForSatelliteModemStateChanged();
         registerForServiceStateChanged();
@@ -549,12 +744,15 @@
         }
 
         mSatellitePlmnListFromOverlayConfig = readSatellitePlmnsFromOverlayConfig();
+        registerApplicationStateChanged();
         updateSupportedSatelliteServicesForActiveSubscriptions();
         mCarrierConfigChangeListener =
                 (slotIndex, subId, carrierId, specificCarrierId) ->
                         handleCarrierConfigChanged(slotIndex, subId, carrierId, specificCarrierId);
-        mCarrierConfigManager.registerCarrierConfigChangeListener(
-                        new HandlerExecutor(new Handler(looper)), mCarrierConfigChangeListener);
+        if (mCarrierConfigManager != null) {
+            mCarrierConfigManager.registerCarrierConfigChangeListener(
+                    new HandlerExecutor(new Handler(looper)), mCarrierConfigChangeListener);
+        }
 
         mConfigDataUpdatedCallback = new ConfigProviderAdaptor.Callback() {
             @Override
@@ -577,6 +775,35 @@
                 getDemoPointingNotAlignedDurationMillisFromResources();
         mSatelliteEmergencyModeDurationMillis =
                 getSatelliteEmergencyModeDurationFromOverlayConfig(context);
+        mEvaluateEsosProfilesPrioritizationDurationMillis =
+                getEvaluateEsosProfilesPrioritizationDurationMillis();
+        sendMessageDelayed(obtainMessage(CMD_EVALUATE_ESOS_PROFILES_PRIORITIZATION),
+                mEvaluateEsosProfilesPrioritizationDurationMillis);
+
+        SubscriptionManager subscriptionManager = mContext.getSystemService(
+                SubscriptionManager.class);
+        mSubscriptionsChangedListener = new SatelliteSubscriptionsChangedListener();
+        if (subscriptionManager != null) {
+            subscriptionManager.addOnSubscriptionsChangedListener(
+                    new HandlerExecutor(new Handler(looper)), mSubscriptionsChangedListener);
+        }
+        registerDefaultSmsSubscriptionChangedBroadcastReceiver();
+        updateSatelliteProvisionedStatePerSubscriberId();
+        if (android.hardware.devicestate.feature.flags.Flags.deviceStatePropertyMigration()) {
+            mDeviceStates = getSupportedDeviceStates();
+        }
+    }
+
+    class SatelliteSubscriptionsChangedListener
+            extends SubscriptionManager.OnSubscriptionsChangedListener {
+
+        /**
+         * Callback invoked when there is any change to any SubscriptionInfo.
+         */
+        @Override
+        public void onSubscriptionsChanged() {
+            handleSubscriptionsChanged();
+        }
     }
 
     /**
@@ -939,11 +1166,12 @@
                     break;
                 }
                 mSatelliteProvisionCallbacks.put(argument.subId, argument.callback);
-                onCompleted = obtainMessage(EVENT_PROVISION_SATELLITE_SERVICE_DONE, request);
                 // Log the current time for provision triggered
                 mProvisionMetricsStats.setProvisioningStartTime();
-                mSatelliteModemInterface.provisionSatelliteService(argument.token,
-                        argument.provisionData, onCompleted);
+                Message provisionSatelliteServiceDoneEvent = this.obtainMessage(
+                        EVENT_PROVISION_SATELLITE_SERVICE_DONE,
+                        new AsyncResult(request, SATELLITE_RESULT_SUCCESS, null));
+                provisionSatelliteServiceDoneEvent.sendToTarget();
                 break;
             }
 
@@ -962,11 +1190,13 @@
                 request = (SatelliteControllerHandlerRequest) msg.obj;
                 ProvisionSatelliteServiceArgument argument =
                         (ProvisionSatelliteServiceArgument) request.argument;
-                onCompleted = obtainMessage(EVENT_DEPROVISION_SATELLITE_SERVICE_DONE, request);
                 if (argument.callback != null) {
                     mProvisionMetricsStats.setProvisioningStartTime();
                 }
-                mSatelliteModemInterface.deprovisionSatelliteService(argument.token, onCompleted);
+                Message deprovisionSatelliteServiceDoneEvent = this.obtainMessage(
+                        EVENT_DEPROVISION_SATELLITE_SERVICE_DONE,
+                        new AsyncResult(request, SATELLITE_RESULT_SUCCESS, null));
+                deprovisionSatelliteServiceDoneEvent.sendToTarget();
                 break;
             }
 
@@ -1003,64 +1233,50 @@
                             + argument.enableSatellite + " was already processed");
                     return;
                 }
-                stopWaitForSatelliteEnablingResponseTimer(argument);
+                if (shouldStopWaitForEnableResponseTimer(argument)) {
+                    stopWaitForSatelliteEnablingResponseTimer(argument);
+                } else {
+                    plogd("Still waiting for the OFF state from modem");
+                }
 
                 if (error == SATELLITE_RESULT_SUCCESS) {
                     if (argument.enableSatellite) {
                         synchronized (mSatelliteEnabledRequestLock) {
                             mWaitingForRadioDisabled = true;
+                            setDemoModeEnabled(argument.enableDemoMode);
                         }
+                        // TODO (b/361139260): Start a timer to wait for other radios off
                         setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_TRUE);
                         setSettingsKeyToAllowDeviceRotation(SATELLITE_MODE_ENABLED_TRUE);
                         evaluateToSendSatelliteEnabledSuccess();
                     } else {
-                        /**
-                         * Unregister Importance Listener for Pointing UI
-                         * when Satellite is disabled
-                         */
+                        // Unregister importance listener for PointingUI when satellite is disabled
                         if (mNeedsSatellitePointing) {
                             mPointingAppController.removeListenerForPointingUI();
                         }
                         synchronized (mSatelliteEnabledRequestLock) {
-                            if (mSatelliteEnabledRequest != null &&
-                                    mSatelliteEnabledRequest.enableSatellite == true &&
-                                    argument.enableSatellite == false && mWaitingForRadioDisabled) {
-                                // Previous mSatelliteEnabledRequest is successful but waiting for
-                                // all radios to be turned off.
-                                mSatelliteEnabledRequest.callback.accept(
-                                        SATELLITE_RESULT_SUCCESS);
-                            }
-                        }
-
-                        synchronized (mIsSatelliteEnabledLock) {
                             if (!mWaitingForSatelliteModemOff) {
                                 moveSatelliteToOffStateAndCleanUpResources(
-                                        SATELLITE_RESULT_SUCCESS,
-                                        argument.callback);
-                            } else {
-                                plogd("Wait for satellite modem off before updating satellite"
-                                        + " modem state");
+                                        SATELLITE_RESULT_SUCCESS);
                             }
                             mWaitingForDisableSatelliteModemResponse = false;
                         }
                     }
-                    // Request Ntn signal strength report when satellite enabled or disabled done.
+                    // Request NTN signal strength report when satellite enabled or disabled done.
                     mLatestRequestedStateForNtnSignalStrengthReport.set(argument.enableSatellite);
                     updateNtnSignalStrengthReporting(argument.enableSatellite);
                 } else {
-                    synchronized (mSatelliteEnabledRequestLock) {
-                        if (mSatelliteEnabledRequest != null &&
-                                mSatelliteEnabledRequest.enableSatellite == true &&
-                                argument.enableSatellite == false && mWaitingForRadioDisabled) {
-                            // Previous mSatelliteEnabledRequest is successful but waiting for
-                            // all radios to be turned off.
-                            mSatelliteEnabledRequest.callback.accept(
-                                    SATELLITE_RESULT_SUCCESS);
-                        }
+                    if (argument.enableSatellite) {
+                        /* Framework need to abort the enable attributes update request if any since
+                         * modem failed to enable satellite.
+                         */
+                        abortSatelliteEnableAttributesUpdateRequest(
+                                SATELLITE_RESULT_REQUEST_ABORTED);
+                        resetSatelliteEnabledRequest();
+                    } else {
+                        resetSatelliteDisabledRequest();
                     }
-                    notifyEnablementFailedToSatelliteSessionController();
-                    resetSatelliteEnabledRequest();
-
+                    notifyEnablementFailedToSatelliteSessionController(argument.enableSatellite);
                     // If Satellite enable/disable request returned Error, no need to wait for radio
                     argument.callback.accept(error);
                 }
@@ -1070,7 +1286,8 @@
                             .setSatelliteTechnology(getSupportedNtnRadioTechnology())
                             .setInitializationProcessingTime(
                                     System.currentTimeMillis() - mSessionProcessingTimeStamp)
-                            .setIsDemoMode(mIsDemoModeEnabled);
+                            .setIsDemoMode(mIsDemoModeEnabled)
+                            .setCarrierId(getSatelliteCarrierId());
                     mSessionProcessingTimeStamp = 0;
 
                     if (error == SATELLITE_RESULT_SUCCESS) {
@@ -1089,22 +1306,110 @@
                             .reportSessionMetrics();
                     mSessionStartTimeStamp = 0;
                     mSessionProcessingTimeStamp = 0;
-
                     mControllerMetricsStats.onSatelliteDisabled();
-
                     handlePersistentLoggingOnSessionEnd(mIsEmergency);
-
-                    synchronized (mIsSatelliteEnabledLock) {
+                    synchronized (mSatelliteEnabledRequestLock) {
                         mWaitingForDisableSatelliteModemResponse = false;
                     }
                 }
                 break;
             }
 
-            case EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT:
+            case EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT: {
                 handleEventWaitForSatelliteEnablingResponseTimedOut(
                         (RequestSatelliteEnabledArgument) msg.obj);
                 break;
+            }
+
+            case CMD_UPDATE_SATELLITE_ENABLE_ATTRIBUTES: {
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                RequestSatelliteEnabledArgument argument =
+                        (RequestSatelliteEnabledArgument) request.argument;
+
+                if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+                    plogd("UpdateEnableAttributes: carrierRoamingNbIotNtn flag is disabled");
+                    sendErrorAndReportSessionMetrics(
+                            SatelliteManager.SATELLITE_RESULT_INVALID_ARGUMENTS, argument.callback);
+                    synchronized (mSatelliteEnabledRequestLock) {
+                        mSatelliteEnableAttributesUpdateRequest = null;
+                    }
+                    break;
+                }
+
+                synchronized (mSatelliteEnabledRequestLock) {
+                    if (mSatelliteEnabledRequest != null) {
+                        plogd("UpdateEnableAttributes: Satellite is being enabled. Need to "
+                                + "wait until enable complete before updating attributes");
+                        break;
+                    }
+                    if (isSatelliteBeingDisabled()) {
+                        plogd("UpdateEnableAttributes: Satellite is being disabled. Aborting the "
+                                + "enable attributes update request");
+                        mSatelliteEnableAttributesUpdateRequest = null;
+                        argument.callback.accept(SATELLITE_RESULT_REQUEST_ABORTED);
+                        break;
+                    }
+                }
+                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(
+                        enableRequestAttributes, onCompleted);
+                startWaitForUpdateSatelliteEnableAttributesResponseTimer(argument);
+                break;
+            }
+
+            case EVENT_UPDATE_SATELLITE_ENABLE_ATTRIBUTES_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                RequestSatelliteEnabledArgument argument =
+                        (RequestSatelliteEnabledArgument) request.argument;
+                int error =  SatelliteServiceUtils.getSatelliteError(
+                        ar, "updateSatelliteEnableAttributes");
+                plogd("EVENT_UPDATE_SATELLITE_ENABLE_ATTRIBUTES_DONE = " + error);
+
+                /*
+                 * The timer to wait for EVENT_UPDATE_SATELLITE_ENABLE_ATTRIBUTES_DONE might have
+                 * expired and thus the request resources might have been cleaned up.
+                 */
+                if (!shouldProcessEventUpdateSatelliteEnableAttributesDone(argument)) {
+                    plogw("The update request ID=" + argument.requestId + " was already processed");
+                    return;
+                }
+                stopWaitForUpdateSatelliteEnableAttributesResponseTimer(argument);
+
+                if (error == SATELLITE_RESULT_SUCCESS) {
+                    setDemoModeEnabled(argument.enableDemoMode);
+                    setEmergencyMode(argument.isEmergency);
+                }
+                synchronized (mSatelliteEnabledRequestLock) {
+                    mSatelliteEnableAttributesUpdateRequest = null;
+                }
+                argument.callback.accept(error);
+                break;
+            }
+
+            case EVENT_WAIT_FOR_UPDATE_SATELLITE_ENABLE_ATTRIBUTES_RESPONSE_TIMED_OUT: {
+                RequestSatelliteEnabledArgument argument =
+                        (RequestSatelliteEnabledArgument) msg.obj;
+                plogw("Timed out to wait for the response from the modem for the request to "
+                        + "update satellite enable attributes, request ID = " + argument.requestId);
+                synchronized (mSatelliteEnabledRequestLock) {
+                    mSatelliteEnableAttributesUpdateRequest = null;
+                }
+                argument.callback.accept(SATELLITE_RESULT_MODEM_TIMEOUT);
+                break;
+            }
 
             case CMD_IS_SATELLITE_ENABLED: {
                 request = (SatelliteControllerHandlerRequest) msg.obj;
@@ -1130,7 +1435,7 @@
                         updateSatelliteEnabledState(enabled, "EVENT_IS_SATELLITE_ENABLED_DONE");
                     }
                 } else if (error == SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED) {
-                    updateSatelliteSupportedStateWhenSatelliteServiceConnected(false);
+                    updateSatelliteSupportedState(false);
                 }
                 ((ResultReceiver) request.argument).send(error, bundle);
                 break;
@@ -1156,7 +1461,7 @@
                         boolean supported = (boolean) ar.result;
                         plogd("isSatelliteSupported: " + supported);
                         bundle.putBoolean(SatelliteManager.KEY_SATELLITE_SUPPORTED, supported);
-                        updateSatelliteSupportedStateWhenSatelliteServiceConnected(supported);
+                        updateSatelliteSupportedState(supported);
                     }
                 }
                 ((ResultReceiver) request.argument).send(error, bundle);
@@ -1185,11 +1490,12 @@
                         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: " + mSatelliteCapabilities);
+                            bundle.putParcelable(SatelliteManager.KEY_SATELLITE_CAPABILITIES,
+                                    mSatelliteCapabilities);
                         }
                     }
                 }
@@ -1236,6 +1542,14 @@
                         mIsRadioOn = true;
                     } else if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF) {
                         resetCarrierRoamingSatelliteModeParams();
+                        synchronized (mIsRadioOnLock) {
+                            if (mRadioOffRequested) {
+                                logd("EVENT_RADIO_STATE_CHANGED: set mIsRadioOn to false");
+                                stopWaitForCellularModemOffTimer();
+                                mIsRadioOn = false;
+                                mRadioOffRequested = false;
+                            }
+                        }
                     }
                 }
 
@@ -1262,8 +1576,10 @@
 
             case CMD_IS_SATELLITE_PROVISIONED: {
                 request = (SatelliteControllerHandlerRequest) msg.obj;
-                onCompleted = obtainMessage(EVENT_IS_SATELLITE_PROVISIONED_DONE, request);
-                mSatelliteModemInterface.requestIsSatelliteProvisioned(onCompleted);
+                Message isProvisionedDoneEvent = this.obtainMessage(
+                        EVENT_IS_SATELLITE_PROVISIONED_DONE,
+                        new AsyncResult(request, SATELLITE_RESULT_SUCCESS, null));
+                isProvisionedDoneEvent.sendToTarget();
                 break;
             }
 
@@ -1272,15 +1588,6 @@
                 break;
             }
 
-            case EVENT_SATELLITE_PROVISION_STATE_CHANGED:
-                ar = (AsyncResult) msg.obj;
-                if (ar.result == null) {
-                    ploge("EVENT_SATELLITE_PROVISION_STATE_CHANGED: result is null");
-                } else {
-                    handleEventSatelliteProvisionStateChanged((boolean) ar.result);
-                }
-                break;
-
             case EVENT_PENDING_DATAGRAMS:
                 plogd("Received EVENT_PENDING_DATAGRAMS");
                 IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
@@ -1289,8 +1596,7 @@
                         plogd("pollPendingSatelliteDatagram result: " + result);
                     }
                 };
-                pollPendingDatagrams(
-                        SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, internalCallback);
+                pollPendingDatagrams(internalCallback);
                 break;
 
             case EVENT_SATELLITE_MODEM_STATE_CHANGED:
@@ -1462,6 +1768,135 @@
                 break;
             }
 
+            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();
+                }
+                break;
+            }
+
+            case CMD_EVALUATE_ESOS_PROFILES_PRIORITIZATION: {
+                evaluateESOSProfilesPrioritization();
+                break;
+            }
+
+            case CMD_UPDATE_PROVISION_SATELLITE_TOKEN: {
+                request = (SatelliteControllerHandlerRequest) msg.obj;
+                RequestProvisionSatelliteArgument argument =
+                        (RequestProvisionSatelliteArgument) request.argument;
+                onCompleted = obtainMessage(EVENT_UPDATE_PROVISION_SATELLITE_TOKEN_DONE, request);
+                boolean provisionChanged = updateSatelliteSubscriptionProvisionState(
+                        argument.mSatelliteSubscriberInfoList, argument.mProvisioned);
+                selectBindingSatelliteSubscription();
+                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);
+                        }
+                    }
+                }
+                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);
+                break;
+            }
+
+            case EVENT_UPDATE_PROVISION_SATELLITE_TOKEN_DONE: {
+                ar = (AsyncResult) msg.obj;
+                request = (SatelliteControllerHandlerRequest) ar.userObj;
+                RequestProvisionSatelliteArgument argument =
+                        (RequestProvisionSatelliteArgument) request.argument;
+                int error = SatelliteServiceUtils.getSatelliteError(ar,
+                        "updateSatelliteSubscription");
+                if (error == SATELLITE_RESULT_SUCCESS) {
+                    synchronized (mSatelliteTokenProvisionedLock) {
+                        mLastConfiguredIccId = argument.getIccId();
+                    }
+                }
+                logd("updateSatelliteSubscription result=" + error);
+                break;
+            }
+
+            case EVENT_WIFI_CONNECTIVITY_STATE_CHANGED: {
+                synchronized (mIsWifiConnectedLock) {
+                    ar = (AsyncResult) msg.obj;
+                    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);
+                break;
+            }
+
+            case EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT: {
+                plogw("Timed out to wait for cellular modem OFF state");
+                synchronized (mIsRadioOnLock) {
+                    mRadioOffRequested = false;
+                }
+                break;
+            }
+
+            case EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT: {
+                // TODO: b/366329504 report carrier roaming metrics for multiple subscription IDs.
+                synchronized (mSupportedSatelliteServicesLock) {
+                    int defaultSubId = mSubscriptionManagerService.getDefaultSubId();
+                    boolean isEntitled = mSatelliteEntitlementStatusPerCarrier.get(defaultSubId,
+                            false);
+                    mCarrierRoamingSatelliteControllerStats.reportIsDeviceEntitled(isEntitled);
+                }
+                sendMessageDelayed(obtainMessage(
+                                EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT),
+                        WAIT_FOR_REPORT_ENTITLED_MERTICS_TIMEOUT_MILLIS);
+                break;
+            }
+
+            case EVENT_SATELLITE_REGISTRATION_FAILURE:
+                ar = (AsyncResult) msg.obj;
+                if (ar.result == null) {
+                    loge("EVENT_SATELLITE_REGISTRATION_FAILURE: result is null");
+                } else {
+                    handleEventSatelliteRegistrationFailure((int) ar.result);
+                }
+                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;
+
             default:
                 Log.w(TAG, "SatelliteControllerHandler: unexpected message code: " +
                         msg.what);
@@ -1469,6 +1904,32 @@
         }
     }
 
+    private static final class RequestProvisionSatelliteArgument {
+        public List<SatelliteSubscriberInfo> mSatelliteSubscriberInfoList;
+        @NonNull
+        public ResultReceiver mResult;
+        public long mRequestId;
+        public String mIccId;
+        public boolean mProvisioned;
+
+        RequestProvisionSatelliteArgument(List<SatelliteSubscriberInfo> satelliteSubscriberInfoList,
+                ResultReceiver result, boolean provisioned) {
+            this.mSatelliteSubscriberInfoList = satelliteSubscriberInfoList;
+            this.mResult = result;
+            this.mProvisioned = provisioned;
+            this.mRequestId = sNextSatelliteEnableRequestId.getAndUpdate(
+                    n -> ((n + 1) % Long.MAX_VALUE));
+        }
+
+        public void setIccId(String iccId) {
+            mIccId = iccId;
+        }
+
+        public String getIccId() {
+            return mIccId;
+        }
+    }
+
     private void handleEventConfigDataUpdated() {
         updateSupportedSatelliteServicesForActiveSubscriptions();
         int[] activeSubIds = mSubscriptionManagerService.getActiveSubIdList(true);
@@ -1493,16 +1954,15 @@
      * enabled, this will also disable the cellular modem, and if the satellite modem is disabled,
      * this will also re-enable the cellular modem.
      *
-     * @param subId The subId of the subscription to set satellite enabled for.
      * @param enableSatellite {@code true} to enable the satellite modem and
      *                        {@code false} to disable.
      * @param enableDemoMode {@code true} to enable demo mode and {@code false} to disable.
      * @param isEmergency {@code true} to enable emergency mode, {@code false} otherwise.
      * @param callback The callback to get the error code of the request.
      */
-    public void requestSatelliteEnabled(int subId, boolean enableSatellite, boolean enableDemoMode,
+    public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
             boolean isEmergency, @NonNull IIntegerConsumer callback) {
-        plogd("requestSatelliteEnabled subId: " + subId + " enableSatellite: " + enableSatellite
+        plogd("requestSatelliteEnabled enableSatellite: " + enableSatellite
                 + " enableDemoMode: " + enableDemoMode + " isEmergency: " + isEmergency);
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
         int error = evaluateOemSatelliteRequestAllowed(true);
@@ -1519,32 +1979,25 @@
                             SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE, result);
                     return;
                 }
+                if (mRadioOffRequested) {
+                    ploge("Radio is being powering off, can not enable satellite");
+                    sendErrorAndReportSessionMetrics(
+                            SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE, result);
+                    return;
+                }
+            }
+
+            if (mTelecomManager.isInEmergencyCall()) {
+                plogd("requestSatelliteEnabled: reject as emergency call is ongoing.");
+                sendErrorAndReportSessionMetrics(
+                        SatelliteManager.SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS, result);
+                return;
             }
         } else {
             /* if disable satellite, always assume demo is also disabled */
             enableDemoMode = false;
         }
 
-        synchronized (mIsSatelliteEnabledLock) {
-            if (mIsSatelliteEnabled != null) {
-                if (mIsSatelliteEnabled == enableSatellite) {
-                    if (enableDemoMode != mIsDemoModeEnabled) {
-                        ploge("Received invalid demo mode while satellite session is enabled"
-                                + " enableDemoMode = " + enableDemoMode);
-                        sendErrorAndReportSessionMetrics(
-                                SatelliteManager.SATELLITE_RESULT_INVALID_ARGUMENTS, result);
-                        return;
-                    } else {
-                        plogd("Enable request matches with current state"
-                                + " enableSatellite = " + enableSatellite);
-                        sendErrorAndReportSessionMetrics(
-                                SatelliteManager.SATELLITE_RESULT_SUCCESS, result);
-                        return;
-                    }
-                }
-            }
-        }
-
         RequestSatelliteEnabledArgument request =
                 new RequestSatelliteEnabledArgument(enableSatellite, enableDemoMode, isEmergency,
                         result);
@@ -1559,35 +2012,147 @@
          *      4. ongoing request = enable, current request = disable: send request to modem
          */
         synchronized (mSatelliteEnabledRequestLock) {
-            if (mSatelliteEnabledRequest == null) {
-                mSatelliteEnabledRequest = request;
-            } else if (mSatelliteEnabledRequest.enableSatellite == request.enableSatellite) {
-                plogd("requestSatelliteEnabled enableSatellite: " + enableSatellite
-                        + " is already in progress.");
-                sendErrorAndReportSessionMetrics(
-                        SatelliteManager.SATELLITE_RESULT_REQUEST_IN_PROGRESS, result);
+            if (!isSatelliteEnabledRequestInProgress()) {
+                synchronized (mIsSatelliteEnabledLock) {
+                    if (mIsSatelliteEnabled != null && mIsSatelliteEnabled == enableSatellite) {
+                        evaluateToUpdateSatelliteEnabledAttributes(result,
+                                SatelliteManager.SATELLITE_RESULT_SUCCESS, request,
+                                mIsDemoModeEnabled, mIsEmergency);
+                        return;
+                    }
+                }
+                if (enableSatellite) {
+                    mSatelliteEnabledRequest = request;
+                } else {
+                    mSatelliteDisabledRequest = request;
+                }
+            } else if (isSatelliteBeingDisabled()) {
+                int resultCode = SatelliteManager.SATELLITE_RESULT_REQUEST_IN_PROGRESS;
+                if (enableSatellite) {
+                    plogw("requestSatelliteEnabled: The enable request cannot be "
+                            + "processed since disable satellite is in progress.");
+                    resultCode = SatelliteManager.SATELLITE_RESULT_DISABLE_IN_PROGRESS;
+                } else {
+                    plogd("requestSatelliteEnabled: Disable is already in progress.");
+                }
+                sendErrorAndReportSessionMetrics(resultCode, result);
                 return;
-            } else if (mSatelliteEnabledRequest.enableSatellite == false
-                    && request.enableSatellite == true) {
-                plogd("requestSatelliteEnabled enableSatellite: " + enableSatellite + " cannot be "
-                        + "processed. Disable satellite is already in progress.");
-                sendErrorAndReportSessionMetrics(
-                        SatelliteManager.SATELLITE_RESULT_ERROR, result);
+            } else {
+                // Satellite is being enabled or satellite enable attributes are being updated
+                if (enableSatellite) {
+                    if (mSatelliteEnableAttributesUpdateRequest == null) {
+                        /* Satellite is being enabled and framework receive a new enable request to
+                         * update the enable attributes.
+                         */
+                        evaluateToUpdateSatelliteEnabledAttributes(result,
+                                SatelliteManager.SATELLITE_RESULT_REQUEST_IN_PROGRESS,
+                                request, mSatelliteEnabledRequest.enableDemoMode,
+                                mSatelliteEnabledRequest.isEmergency);
+                    } else {
+                        /* The enable attributes update request is already being processed.
+                         * Framework can't handle one more request to update enable attributes.
+                         */
+                        plogd("requestSatelliteEnabled: enable attributes update request is already"
+                                + " in progress.");
+                        sendErrorAndReportSessionMetrics(
+                                SatelliteManager.SATELLITE_RESULT_REQUEST_IN_PROGRESS, result);
+                    }
+                    return;
+                } else {
+                    /* Users might want to end the satellite session while it is being enabled, or
+                     * the satellite session need to be disabled for an emergency call. Note: some
+                     * carriers want to disable satellite for prioritizing emergency calls. Thus,
+                     * we need to push the disable request to modem while enable is in progress.
+                     */
+                    if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+                        plogd("requestSatelliteEnabled: carrierRoamingNbIotNtn flag is disabled");
+                        sendErrorAndReportSessionMetrics(
+                                SatelliteManager.SATELLITE_RESULT_ENABLE_IN_PROGRESS, result);
+                        return;
+                    }
+                    mSatelliteDisabledRequest = request;
+                }
+            }
+        }
+        sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, null);
+    }
+
+    /**
+     * Validate the newly-received enable attributes against the current ones. If the new attributes
+     * are valid and different from the current ones, framework will send a request to update the
+     * enable attributes to modem. Otherwise, framework will return
+     * {@code SATELLITE_RESULT_INVALID_ARGUMENTS} to the requesting clients.
+     *
+     * @param result The callback that returns the result to the requesting client.
+     * @param resultCode The result code to send back to the requesting client when framework does
+     *                   not need to reconfigure modem.
+     * @param enableRequest The new enable request to update satellite enable attributes.
+     * @param currentDemoMode The current demo mode at framework.
+     * @param currentEmergencyMode The current emergency mode at framework.
+     */
+    private void evaluateToUpdateSatelliteEnabledAttributes(@NonNull Consumer<Integer> result,
+            @SatelliteManager.SatelliteResult int resultCode,
+            @NonNull RequestSatelliteEnabledArgument enableRequest, boolean currentDemoMode,
+            boolean currentEmergencyMode) {
+        boolean needToReconfigureModem = false;
+        if (enableRequest.enableDemoMode != currentDemoMode) {
+            if (enableRequest.enableDemoMode) {
+                ploge("Moving from real mode to demo mode is rejected");
+                sendErrorAndReportSessionMetrics(SATELLITE_RESULT_INVALID_ARGUMENTS, result);
                 return;
+            } else {
+                plogd("Moving from demo mode to real mode. Need to reconfigure"
+                        + " modem with real mode");
+                needToReconfigureModem = true;
+            }
+        } else if (enableRequest.isEmergency != currentEmergencyMode) {
+            if (enableRequest.isEmergency) {
+                plogd("Moving from non-emergency to emergency mode. Need to "
+                        + "reconfigure modem");
+                needToReconfigureModem = true;
+            } else {
+                plogd("Non-emergency requests can be served during an emergency"
+                        + " satellite session. No need to reconfigure modem.");
             }
         }
 
-        sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, null);
+        if (needToReconfigureModem) {
+            synchronized (mSatelliteEnabledRequestLock) {
+                mSatelliteEnableAttributesUpdateRequest = enableRequest;
+            }
+            sendRequestAsync(
+                    CMD_UPDATE_SATELLITE_ENABLE_ATTRIBUTES, enableRequest, null);
+        } else {
+            if (resultCode != SatelliteManager.SATELLITE_RESULT_SUCCESS) {
+                plogd("requestSatelliteEnabled enable satellite is already in progress.");
+            }
+            sendErrorAndReportSessionMetrics(resultCode, result);
+        }
+        return;
+    }
+
+    /**
+     * @return {@code true} when either enable request, disable request, or enable attributes update
+     * request is in progress, {@code false} otherwise.
+     */
+    private boolean isSatelliteEnabledRequestInProgress() {
+        synchronized (mSatelliteEnabledRequestLock) {
+            plogd("mSatelliteEnabledRequest: " + (mSatelliteEnabledRequest != null)
+                    + ", mSatelliteDisabledRequest: " + (mSatelliteDisabledRequest != null)
+                    + ", mSatelliteEnableAttributesUpdateRequest: "
+                    + (mSatelliteEnableAttributesUpdateRequest != null));
+            return (mSatelliteEnabledRequest != null || mSatelliteDisabledRequest != null
+                    || mSatelliteEnableAttributesUpdateRequest != null);
+        }
     }
 
     /**
      * Request to get whether the satellite modem is enabled.
      *
-     * @param subId The subId of the subscription to check whether satellite is enabled for.
      * @param result The result receiver that returns whether the satellite modem is enabled
      *               if the request is successful or an error code if the request failed.
      */
-    public void requestIsSatelliteEnabled(int subId, @NonNull ResultReceiver result) {
+    public void requestIsSatelliteEnabled(@NonNull ResultReceiver result) {
         int error = evaluateOemSatelliteRequestAllowed(false);
         if (error != SATELLITE_RESULT_SUCCESS) {
             result.send(error, null);
@@ -1613,13 +2178,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;
+        }
     }
 
     /**
@@ -1627,27 +2194,56 @@
      *
      * @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();
+    }
+
+    /**
+     * Get whether satellite modem is being disabled.
+     *
+     * @return {@code true} if the satellite modem is being disabled and {@code false} otherwise.
+     */
+    public boolean isSatelliteBeingDisabled() {
+        if (mSatelliteSessionController != null
+                && mSatelliteSessionController.isInDisablingState()) {
+            return true;
+        }
+
+        synchronized (mSatelliteEnabledRequestLock) {
+            return (mSatelliteDisabledRequest != null);
+        }
     }
 
     /**
      * Request to get whether the satellite service demo mode is enabled.
      *
-     * @param subId The subId of the subscription to check whether the satellite demo mode
-     *              is enabled for.
      * @param result The result receiver that returns whether the satellite demo mode is enabled
      *               if the request is successful or an error code if the request failed.
      */
-    public void requestIsDemoModeEnabled(int subId, @NonNull ResultReceiver result) {
+    public void requestIsDemoModeEnabled(@NonNull ResultReceiver result) {
         int error = evaluateOemSatelliteRequestAllowed(true);
         if (error != SATELLITE_RESULT_SUCCESS) {
             result.send(error, null);
@@ -1673,13 +2269,33 @@
     }
 
     /**
+     * Request to get whether the satellite enabled request is for emergency or not.
+     *
+     * @param result The result receiver that returns whether the request is for emergency
+     *               if the request is successful or an error code if the request failed.
+     */
+    public void requestIsEmergencyModeEnabled(@NonNull ResultReceiver result) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            plogd("requestIsEmergencyModeEnabled: oemEnabledSatelliteFlag is disabled");
+            result.send(SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED, null);
+            return;
+        }
+
+        synchronized (mSatelliteEnabledRequestLock) {
+            Bundle bundle = new Bundle();
+            bundle.putBoolean(SatelliteManager.KEY_EMERGENCY_MODE_ENABLED,
+                    getRequestIsEmergency());
+            result.send(SATELLITE_RESULT_SUCCESS, bundle);
+        }
+    }
+
+    /**
      * Request to get whether the satellite service is supported on the device.
      *
-     * @param subId The subId of the subscription to check satellite service support for.
      * @param result The result receiver that returns whether the satellite service is supported on
      *               the device if the request is successful or an error code if the request failed.
      */
-    public void requestIsSatelliteSupported(int subId, @NonNull ResultReceiver result) {
+    public void requestIsSatelliteSupported(@NonNull ResultReceiver result) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             plogd("requestIsSatelliteSupported: oemEnabledSatelliteFlag is disabled");
             result.send(SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED, null);
@@ -1690,7 +2306,7 @@
                 /* We have already successfully queried the satellite modem. */
                 Bundle bundle = new Bundle();
                 bundle.putBoolean(SatelliteManager.KEY_SATELLITE_SUPPORTED, mIsSatelliteSupported);
-                bundle.putInt(SATELLITE_SUBSCRIPTION_ID, subId);
+                bundle.putInt(SATELLITE_SUBSCRIPTION_ID, getSelectedSatelliteSubId());
                 result.send(SATELLITE_RESULT_SUCCESS, bundle);
                 return;
             }
@@ -1702,11 +2318,10 @@
     /**
      * Request to get the {@link SatelliteCapabilities} of the satellite service.
      *
-     * @param subId The subId of the subscription to get the satellite capabilities for.
      * @param result The result receiver that returns the {@link SatelliteCapabilities}
      *               if the request is successful or an error code if the request failed.
      */
-    public void requestSatelliteCapabilities(int subId, @NonNull ResultReceiver result) {
+    public void requestSatelliteCapabilities(@NonNull ResultReceiver result) {
         int error = evaluateOemSatelliteRequestAllowed(false);
         if (error != SATELLITE_RESULT_SUCCESS) {
             result.send(error, null);
@@ -1731,11 +2346,10 @@
      * This can be called by the pointing UI when the user starts pointing to the satellite.
      * Modem should continue to report the pointing input as the device or satellite moves.
      *
-     * @param subId The subId of the subscription to start satellite transmission updates for.
      * @param errorCallback The callback to get the error code of the request.
      * @param callback The callback to notify of satellite transmission updates.
      */
-    public void startSatelliteTransmissionUpdates(int subId,
+    public void startSatelliteTransmissionUpdates(
             @NonNull IIntegerConsumer errorCallback,
             @NonNull ISatelliteTransmissionUpdateCallback callback) {
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(errorCallback::accept);
@@ -1745,7 +2359,7 @@
             return;
         }
 
-        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
+        final int validSubId = getSelectedSatelliteSubId();
         mPointingAppController.registerForSatelliteTransmissionUpdates(validSubId, callback);
         sendRequestAsync(CMD_START_SATELLITE_TRANSMISSION_UPDATES,
                 new SatelliteTransmissionUpdateArgument(result, callback, validSubId), null);
@@ -1755,23 +2369,15 @@
      * Stop receiving satellite transmission updates.
      * This can be called by the pointing UI when the user stops pointing to the satellite.
      *
-     * @param subId The subId of the subscription to stop satellite transmission updates for.
      * @param errorCallback The callback to get the error code of the request.
      * @param callback The callback that was passed to {@link #startSatelliteTransmissionUpdates(
      *                 int, IIntegerConsumer, ISatelliteTransmissionUpdateCallback)}.
      */
-    public void stopSatelliteTransmissionUpdates(int subId, @NonNull IIntegerConsumer errorCallback,
+    public void stopSatelliteTransmissionUpdates(@NonNull IIntegerConsumer errorCallback,
             @NonNull ISatelliteTransmissionUpdateCallback callback) {
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(errorCallback::accept);
-        int error = evaluateOemSatelliteRequestAllowed(true);
-        if (error != SATELLITE_RESULT_SUCCESS) {
-            result.accept(error);
-            return;
-        }
-
-        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
         mPointingAppController.unregisterForSatelliteTransmissionUpdates(
-                validSubId, 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
@@ -1783,7 +2389,6 @@
      * Register the subscription with a satellite provider.
      * This is needed to register the subscription if the provider allows dynamic registration.
      *
-     * @param subId The subId of the subscription to be provisioned.
      * @param token The token to be used as a unique identifier for provisioning with satellite
      *              gateway.
      * @param provisionData Data from the provisioning app that can be used by provisioning server
@@ -1792,40 +2397,64 @@
      * @return The signal transport used by the caller to cancel the provision request,
      *         or {@code null} if the request failed.
      */
-    @Nullable public ICancellationSignal provisionSatelliteService(int subId,
+    @Nullable public ICancellationSignal provisionSatelliteService(
             @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 = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
-        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;
     }
 
     /**
@@ -1835,51 +2464,73 @@
      * #onSatelliteProvisionStateChanged(boolean)}
      * should report as deprovisioned.
      *
-     * @param subId The subId of the subscription to be deprovisioned.
      * @param token The token of the device/subscription to be deprovisioned.
      * @param callback The callback to get the error code of the request.
      */
-    public void deprovisionSatelliteService(int subId,
+    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;
+            }
 
-        Boolean satelliteProvisioned = isSatelliteViaOemProvisioned();
-        if (satelliteProvisioned == null) {
-            result.accept(SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
-            return;
-        }
-        if (!satelliteProvisioned) {
-            result.accept(SATELLITE_RESULT_SUCCESS);
-            return;
-        }
+            if (Boolean.FALSE.equals(isDeviceProvisioned())) {
+                result.accept(SATELLITE_RESULT_SUCCESS);
+                return;
+            }
 
-        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
-        sendRequestAsync(CMD_DEPROVISION_SATELLITE_SERVICE,
-                new ProvisionSatelliteServiceArgument(token, null, result, validSubId),
+            sendRequestAsync(CMD_DEPROVISION_SATELLITE_SERVICE,
+                new ProvisionSatelliteServiceArgument(token, null,
+                        result, getSelectedSatelliteSubId()),
                 null);
+        }
     }
 
     /**
      * Registers for the satellite provision state changed.
      *
-     * @param subId The subId of the subscription to register for provision state changed.
      * @param callback The callback to handle the satellite provision state changed event.
      *
      * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
      */
     @SatelliteManager.SatelliteResult public int registerForSatelliteProvisionStateChanged(
-            int subId, @NonNull ISatelliteProvisionStateCallback callback) {
+            @NonNull ISatelliteProvisionStateCallback callback) {
         int error = evaluateOemSatelliteRequestAllowed(false);
         if (error != SATELLITE_RESULT_SUCCESS) {
             return error;
         }
 
         mSatelliteProvisionStateChangedListeners.put(callback.asBinder(), callback);
+
+        boolean isProvisioned = Boolean.TRUE.equals(isDeviceProvisioned());
+        try {
+            callback.onSatelliteProvisionStateChanged(isProvisioned);
+        } catch (RemoteException ex) {
+            loge("registerForSatelliteProvisionStateChanged: " + ex);
+        }
+        synchronized (mDeviceProvisionLock) {
+            plogd("registerForSatelliteProvisionStateChanged: report current provisioned "
+                    + "state, state=" + isProvisioned);
+        }
+
         return SATELLITE_RESULT_SUCCESS;
     }
 
@@ -1887,12 +2538,11 @@
      * Unregisters for the satellite provision state changed.
      * If callback was not registered before, the request will be ignored.
      *
-     * @param subId The subId of the subscription to unregister for provision state changed.
      * @param callback The callback that was passed to
      * {@link #registerForSatelliteProvisionStateChanged(int, ISatelliteProvisionStateCallback)}.
      */
     public void unregisterForSatelliteProvisionStateChanged(
-            int subId, @NonNull ISatelliteProvisionStateCallback callback) {
+            @NonNull ISatelliteProvisionStateCallback callback) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             plogd("unregisterForSatelliteProvisionStateChanged: "
                     + "oemEnabledSatelliteFlag is disabled");
@@ -1904,23 +2554,22 @@
     /**
      * Request to get whether the device is provisioned with a satellite provider.
      *
-     * @param subId The subId of the subscription to get whether the device is provisioned for.
      * @param result The result receiver that returns whether the device is provisioned with a
      *               satellite provider if the request is successful or an error code if the
      *               request failed.
      */
-    public void requestIsSatelliteProvisioned(int subId, @NonNull ResultReceiver result) {
+    public void requestIsSatelliteProvisioned(@NonNull ResultReceiver result) {
         int error = evaluateOemSatelliteRequestAllowed(false);
         if (error != SATELLITE_RESULT_SUCCESS) {
             result.send(error, null);
             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;
             }
@@ -1932,17 +2581,21 @@
     /**
      * Registers for modem state changed from satellite modem.
      *
-     * @param subId The subId of the subscription to register for satellite modem state changed.
      * @param callback The callback to handle the satellite modem state changed event.
      *
      * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
      */
-    @SatelliteManager.SatelliteResult public int registerForSatelliteModemStateChanged(int subId,
+    @SatelliteManager.SatelliteResult public int registerForSatelliteModemStateChanged(
             @NonNull ISatelliteModemStateCallback callback) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             plogd("registerForSatelliteModemStateChanged: oemEnabledSatelliteFlag is disabled");
             return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
         }
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("registerForSatelliteModemStateChanged: add Listeners for ModemState");
+            mSatelliteRegistrationFailureListeners.put(callback.asBinder(), callback);
+            mTerrestrialNetworkAvailableChangedListeners.put(callback.asBinder(), callback);
+        }
         if (mSatelliteSessionController != null) {
             mSatelliteSessionController.registerForSatelliteModemStateChanged(callback);
         } else {
@@ -1957,11 +2610,10 @@
      * Unregisters for modem state changed from satellite modem.
      * If callback was not registered before, the request will be ignored.
      *
-     * @param subId The subId of the subscription to unregister for satellite modem state changed.
      * @param callback The callback that was passed to
      * {@link #registerForSatelliteModemStateChanged(int, ISatelliteModemStateCallback)}.
      */
-    public void unregisterForModemStateChanged(int subId,
+    public void unregisterForModemStateChanged(
             @NonNull ISatelliteModemStateCallback callback) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             plogd("unregisterForModemStateChanged: oemEnabledSatelliteFlag is disabled");
@@ -1973,17 +2625,21 @@
             ploge("unregisterForModemStateChanged: mSatelliteSessionController"
                     + " is not initialized yet");
         }
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("unregisterForModemStateChanged: remove Listeners for ModemState");
+            mSatelliteRegistrationFailureListeners.remove(callback.asBinder());
+            mTerrestrialNetworkAvailableChangedListeners.remove(callback.asBinder());
+        }
     }
 
     /**
      * Register to receive incoming datagrams over satellite.
      *
-     * @param subId The subId of the subscription to register for incoming satellite datagrams.
      * @param callback The callback to handle incoming datagrams over satellite.
      *
      * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
      */
-    @SatelliteManager.SatelliteResult public int registerForIncomingDatagram(int subId,
+    @SatelliteManager.SatelliteResult public int registerForIncomingDatagram(
             @NonNull ISatelliteDatagramCallback callback) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             plogd("registerForIncomingDatagram: oemEnabledSatelliteFlag is disabled");
@@ -1993,18 +2649,18 @@
             return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
         }
         plogd("registerForIncomingDatagram: callback=" + callback);
-        return mDatagramController.registerForSatelliteDatagram(subId, callback);
+        return mDatagramController.registerForSatelliteDatagram(
+                getSelectedSatelliteSubId(), callback);
     }
 
     /**
      * Unregister to stop receiving incoming datagrams over satellite.
      * If callback was not registered before, the request will be ignored.
      *
-     * @param subId The subId of the subscription to unregister for incoming satellite datagrams.
      * @param callback The callback that was passed to
      *                 {@link #registerForIncomingDatagram(int, ISatelliteDatagramCallback)}.
      */
-    public void unregisterForIncomingDatagram(int subId,
+    public void unregisterForIncomingDatagram(
             @NonNull ISatelliteDatagramCallback callback) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             plogd("unregisterForIncomingDatagram: oemEnabledSatelliteFlag is disabled");
@@ -2014,7 +2670,8 @@
             return;
         }
         plogd("unregisterForIncomingDatagram: callback=" + callback);
-        mDatagramController.unregisterForSatelliteDatagram(subId, callback);
+        mDatagramController.unregisterForSatelliteDatagram(
+                getSelectedSatelliteSubId(), callback);
     }
 
     /**
@@ -2025,10 +2682,9 @@
      * {@link android.telephony.satellite.SatelliteDatagramCallback#onSatelliteDatagramReceived(
      * long, SatelliteDatagram, int, Consumer)}
      *
-     * @param subId The subId of the subscription used for receiving datagrams.
      * @param callback The callback to get {@link SatelliteManager.SatelliteResult} of the request.
      */
-    public void pollPendingDatagrams(int subId, @NonNull IIntegerConsumer callback) {
+    public void pollPendingDatagrams(@NonNull IIntegerConsumer callback) {
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
         int error = evaluateOemSatelliteRequestAllowed(true);
         if (error != SATELLITE_RESULT_SUCCESS) {
@@ -2036,8 +2692,8 @@
             return;
         }
 
-        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
-        mDatagramController.pollPendingSatelliteDatagrams(validSubId, result);
+        mDatagramController.pollPendingSatelliteDatagrams(
+                getSelectedSatelliteSubId(), result);
     }
 
     /**
@@ -2047,7 +2703,6 @@
      * input to this method. Datagram received here will be passed down to modem without any
      * encoding or encryption.
      *
-     * @param subId The subId of the subscription to send satellite datagrams for.
      * @param datagramType datagram type indicating whether the datagram is of type
      *                     SOS_SMS or LOCATION_SHARING.
      * @param datagram encoded gateway datagram which is encrypted by the caller.
@@ -2056,10 +2711,10 @@
      *                                 full screen mode.
      * @param callback The callback to get {@link SatelliteManager.SatelliteResult} of the request.
      */
-    public void sendDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
+    public void sendDatagram(@SatelliteManager.DatagramType int datagramType,
             SatelliteDatagram datagram, boolean needFullScreenPointingUI,
             @NonNull IIntegerConsumer callback) {
-        plogd("sendSatelliteDatagram: subId: " + subId + " datagramType: " + datagramType
+        plogd("sendSatelliteDatagram: datagramType: " + datagramType
                 + " needFullScreenPointingUI: " + needFullScreenPointingUI);
 
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
@@ -2078,19 +2733,17 @@
                     mIsEmergency);
         }
 
-        final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
-        mDatagramController.sendSatelliteDatagram(validSubId, datagramType, datagram,
-                needFullScreenPointingUI, result);
+        mDatagramController.sendSatelliteDatagram(getSelectedSatelliteSubId(), datagramType,
+                datagram, needFullScreenPointingUI, result);
     }
 
     /**
      * Request to get the time after which the satellite will be visible.
      *
-     * @param subId The subId to get the time after which the satellite will be visible for.
      * @param result The result receiver that returns the time after which the satellite will
      *               be visible if the request is successful or an error code if the request failed.
      */
-    public void requestTimeForNextSatelliteVisibility(int subId, @NonNull ResultReceiver result) {
+    public void requestTimeForNextSatelliteVisibility(@NonNull ResultReceiver result) {
         int error = evaluateOemSatelliteRequestAllowed(true);
         if (error != SATELLITE_RESULT_SUCCESS) {
             result.send(error, null);
@@ -2101,12 +2754,11 @@
     }
 
     /**
-     * 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 subId The subId of the subscription.
      * @param isAligned {@true} means device is aligned with the satellite, otherwise {@false}.
      */
-    public void setDeviceAlignedWithSatellite(@NonNull int subId, @NonNull boolean isAligned) {
+    public void setDeviceAlignedWithSatellite(@NonNull boolean isAligned) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             plogd("setDeviceAlignedWithSatellite: oemEnabledSatelliteFlag is disabled");
             return;
@@ -2114,6 +2766,12 @@
 
         DemoSimulator.getInstance().setDeviceAlignedWithSatellite(isAligned);
         mDatagramController.setDeviceAlignedWithSatellite(isAligned);
+        if (mSatelliteSessionController != null) {
+            mSatelliteSessionController.setDeviceAlignedWithSatellite(isAligned);
+        } else {
+            ploge("setDeviceAlignedWithSatellite: mSatelliteSessionController"
+                    + " is not initialized yet");
+        }
     }
 
     /**
@@ -2220,11 +2878,10 @@
     /**
      * Request to get the signal strength of the satellite connection.
      *
-     * @param subId The subId of the subscription to request for.
      * @param result Result receiver to get the error code of the request and the current signal
      * strength of the satellite connection.
      */
-    public void requestNtnSignalStrength(int subId, @NonNull ResultReceiver result) {
+    public void requestNtnSignalStrength(@NonNull ResultReceiver result) {
         if (DBG) plogd("requestNtnSignalStrength()");
 
         int error = evaluateOemSatelliteRequestAllowed(true);
@@ -2253,7 +2910,6 @@
      * is not successful, a {@link ServiceSpecificException} that contains
      * {@link SatelliteManager.SatelliteResult} will be thrown.
      *
-     * @param subId The id of the subscription to request for.
      * @param callback The callback to handle the NTN signal strength changed event. If the
      * operation is successful, {@link INtnSignalStrengthCallback#onNtnSignalStrengthChanged(
      * NtnSignalStrength)} will return an instance of {@link NtnSignalStrength} with a value of
@@ -2262,11 +2918,11 @@
      *
      * @throws ServiceSpecificException If the callback registration operation fails.
      */
-    public void registerForNtnSignalStrengthChanged(int subId,
+    public void registerForNtnSignalStrengthChanged(
             @NonNull INtnSignalStrengthCallback callback) throws RemoteException {
         if (DBG) plogd("registerForNtnSignalStrengthChanged()");
 
-        int error = evaluateOemSatelliteRequestAllowed(true);
+        int error = evaluateOemSatelliteRequestAllowed(false);
         if (error == SATELLITE_RESULT_SUCCESS) {
             mNtnSignalStrengthChangedListeners.put(callback.asBinder(), callback);
             synchronized (mNtnSignalsStrengthLock) {
@@ -2287,34 +2943,28 @@
      * Unregisters for NTN signal strength changed from satellite modem.
      * If callback was not registered before, the request will be ignored.
      *
-     * @param subId The id of the subscription to unregister for listening NTN signal strength
      * changed event.
      * @param callback The callback that was passed to
      * {@link #registerForNtnSignalStrengthChanged(int, INtnSignalStrengthCallback)}
      */
     public void unregisterForNtnSignalStrengthChanged(
-            int subId, @NonNull INtnSignalStrengthCallback callback) {
+            @NonNull INtnSignalStrengthCallback callback) {
         if (DBG) plogd("unregisterForNtnSignalStrengthChanged()");
-
-        int error = evaluateOemSatelliteRequestAllowed(true);
-        if (error == SATELLITE_RESULT_SUCCESS) {
-            mNtnSignalStrengthChangedListeners.remove(callback.asBinder());
-        }
+        mNtnSignalStrengthChangedListeners.remove(callback.asBinder());
     }
 
     /**
      * Registers for satellite capabilities change event from the satellite service.
      *
-     * @param subId The id of the subscription to request for.
      * @param callback The callback to handle the satellite capabilities changed event.
      *
      * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
      */
     @SatelliteManager.SatelliteResult public int registerForCapabilitiesChanged(
-            int subId, @NonNull ISatelliteCapabilitiesCallback callback) {
+            @NonNull ISatelliteCapabilitiesCallback callback) {
         if (DBG) plogd("registerForCapabilitiesChanged()");
 
-        int error = evaluateOemSatelliteRequestAllowed(true);
+        int error = evaluateOemSatelliteRequestAllowed(false);
         if (error != SATELLITE_RESULT_SUCCESS) return error;
 
         mSatelliteCapabilitiesChangedListeners.put(callback.asBinder(), callback);
@@ -2325,31 +2975,25 @@
      * Unregisters for satellite capabilities change event from the satellite service.
      * If callback was not registered before, the request will be ignored.
      *
-     * @param subId The id of the subscription to unregister for listening satellite capabilities
      * changed event.
      * @param callback The callback that was passed to
      * {@link #registerForCapabilitiesChanged(int, ISatelliteCapabilitiesCallback)}
      */
     public void unregisterForCapabilitiesChanged(
-            int subId, @NonNull ISatelliteCapabilitiesCallback callback) {
+            @NonNull ISatelliteCapabilitiesCallback callback) {
         if (DBG) plogd("unregisterForCapabilitiesChanged()");
-
-        int error = evaluateOemSatelliteRequestAllowed(true);
-        if (error == SATELLITE_RESULT_SUCCESS) {
-            mSatelliteCapabilitiesChangedListeners.remove(callback.asBinder());
-        }
+        mSatelliteCapabilitiesChangedListeners.remove(callback.asBinder());
     }
 
     /**
      * Registers for the satellite supported state changed.
      *
-     * @param subId The subId of the subscription to register for supported state changed.
      * @param callback The callback to handle the satellite supported state changed event.
      *
      * @return The {@link SatelliteManager.SatelliteResult} result of the operation.
      */
     @SatelliteManager.SatelliteResult public int registerForSatelliteSupportedStateChanged(
-            int subId, @NonNull ISatelliteSupportedStateCallback callback) {
+            @NonNull ISatelliteSupportedStateCallback callback) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             plogd("registerForSatelliteSupportedStateChanged: oemEnabledSatelliteFlag is disabled");
             return SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
@@ -2363,12 +3007,11 @@
      * Unregisters for the satellite supported state changed.
      * If callback was not registered before, the request will be ignored.
      *
-     * @param subId The subId of the subscription to unregister for supported state changed.
      * @param callback The callback that was passed to
      * {@link #registerForSatelliteSupportedStateChanged(int, ISatelliteSupportedStateCallback)}.
      */
     public void unregisterForSatelliteSupportedStateChanged(
-            int subId, @NonNull ISatelliteSupportedStateCallback callback) {
+            @NonNull ISatelliteSupportedStateCallback callback) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             plogd("unregisterForSatelliteSupportedStateChanged: "
                     + "oemEnabledSatelliteFlag is disabled");
@@ -2381,26 +3024,28 @@
      * 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.
+     * @param provisioned          Whether satellite should be provisioned or not.
      * @return {@code true} if the satellite vendor service is set successfully,
      * {@code false} otherwise.
      */
-    public boolean setSatelliteServicePackageName(@Nullable String servicePackageName) {
-        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            plogd("setSatelliteServicePackageName: oemEnabledSatelliteFlag is disabled");
-            return false;
-        }
+    public boolean setSatelliteServicePackageName(@Nullable String servicePackageName,
+            String provisioned) {
         if (!isMockModemAllowed()) {
             plogd("setSatelliteServicePackageName: mock modem not allowed");
             return false;
         }
 
         // Cached states need to be cleared whenever switching satellite vendor services.
-        plogd("setSatelliteServicePackageName: Resetting cached states");
+        plogd("setSatelliteServicePackageName: Resetting cached states, provisioned="
+                + provisioned);
         synchronized (mIsSatelliteSupportedLock) {
             mIsSatelliteSupported = null;
         }
-        synchronized (mSatelliteViaOemProvisionLock) {
-            mIsSatelliteViaOemProvisioned = null;
+        synchronized (mDeviceProvisionLock) {
+            mIsDeviceProvisioned = Optional.ofNullable(provisioned)
+                    .filter(s -> s.equalsIgnoreCase("true") || s.equalsIgnoreCase("false"))
+                    .map(s -> s.equalsIgnoreCase("true"))
+                    .orElse(null);
         }
         synchronized (mIsSatelliteEnabledLock) {
             mIsSatelliteEnabled = null;
@@ -2433,6 +3078,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.
      *
@@ -2511,6 +3171,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;
@@ -2597,7 +3265,7 @@
             ploge("setOemEnabledSatelliteProvisionStatus: mock modem not allowed");
             return false;
         }
-        synchronized (mSatelliteViaOemProvisionLock) {
+        synchronized (mDeviceProvisionLock) {
             if (reset) {
                 mOverriddenIsSatelliteViaOemProvisioned = null;
             } else {
@@ -2642,21 +3310,20 @@
             plogd("onSatelliteServiceConnected: oemEnabledSatelliteFlag is disabled");
             return;
         }
+
         if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
-            synchronized (mIsSatelliteSupportedLock) {
-                if (mIsSatelliteSupported == null) {
-                    ResultReceiver receiver = new ResultReceiver(this) {
-                        @Override
-                        protected void onReceiveResult(
-                                int resultCode, Bundle resultData) {
-                            plogd("onSatelliteServiceConnected.requestIsSatelliteSupported:"
-                                    + " resultCode=" + resultCode);
-                        }
-                    };
-                    requestIsSatelliteSupported(
-                            SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, receiver);
+            plogd("onSatelliteServiceConnected");
+            // Vendor service might have just come back from a crash
+            moveSatelliteToOffStateAndCleanUpResources(SATELLITE_RESULT_MODEM_ERROR);
+            ResultReceiver receiver = new ResultReceiver(this) {
+                @Override
+                protected void onReceiveResult(
+                        int resultCode, Bundle resultData) {
+                    plogd("onSatelliteServiceConnected.requestIsSatelliteSupported:"
+                            + " resultCode=" + resultCode);
                 }
-            }
+            };
+            requestIsSatelliteSupported(receiver);
         } else {
             plogd("onSatelliteServiceConnected: Satellite vendor service is not supported."
                     + " Ignored the event");
@@ -2665,27 +3332,80 @@
 
     /**
      * This function is used by {@link com.android.internal.telephony.ServiceStateTracker} to notify
-     * {@link SatelliteController} that it has received a request to power off the cellular radio
-     * modem. {@link SatelliteController} will then power off the satellite modem.
+     * {@link SatelliteController} that it has received a request to power on or off the cellular
+     * radio modem.
+     *
+     * @param powerOn {@code true} means cellular radio is about to be powered on, {@code false}
+     *                 means cellular modem is about to be powered off.
      */
-    public void onCellularRadioPowerOffRequested() {
-        logd("onCellularRadioPowerOffRequested()");
+    public void onSetCellularRadioPowerStateRequested(boolean powerOn) {
+        logd("onSetCellularRadioPowerStateRequested: powerOn=" + powerOn);
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            plogd("onCellularRadioPowerOffRequested: oemEnabledSatelliteFlag is disabled");
+            plogd("onSetCellularRadioPowerStateRequested: oemEnabledSatelliteFlag is disabled");
             return;
         }
 
         synchronized (mIsRadioOnLock) {
-            mIsRadioOn = false;
+            mRadioOffRequested = !powerOn;
         }
-        requestSatelliteEnabled(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                false /* enableSatellite */, false /* enableDemoMode */, false /* isEmergency */,
-                new IIntegerConsumer.Stub() {
-                    @Override
-                    public void accept(int result) {
-                        plogd("onRadioPowerOffRequested: requestSatelliteEnabled result=" + result);
-                    }
-                });
+        if (powerOn) {
+            stopWaitForCellularModemOffTimer();
+        } else {
+            requestSatelliteEnabled(
+                    false /* enableSatellite */, false /* enableDemoMode */,
+                    false /* isEmergency */,
+                    new IIntegerConsumer.Stub() {
+                        @Override
+                        public void accept(int result) {
+                            plogd("onSetCellularRadioPowerStateRequested: requestSatelliteEnabled"
+                                    + " result=" + result);
+                        }
+                    });
+            startWaitForCellularModemOffTimer();
+        }
+    }
+
+    /**
+     * This function is used by {@link com.android.internal.telephony.ServiceStateTracker} to notify
+     * {@link SatelliteController} that the request to power off the cellular radio modem has
+     * failed.
+     */
+    public void onPowerOffCellularRadioFailed() {
+        logd("onPowerOffCellularRadioFailed");
+        synchronized (mIsRadioOnLock) {
+            mRadioOffRequested = false;
+            stopWaitForCellularModemOffTimer();
+        }
+    }
+
+    /**
+     * 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");
+        }
     }
 
     /**
@@ -2729,7 +3449,7 @@
      * 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<>();
@@ -2839,7 +3559,7 @@
      * @return {@code Pair<true, subscription ID>} if any subscription on the device is connected to
      * satellite, {@code Pair<false, null>} otherwise.
      */
-    private Pair<Boolean, Integer> isUsingNonTerrestrialNetworkViaCarrier() {
+    Pair<Boolean, Integer> isUsingNonTerrestrialNetworkViaCarrier() {
         if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
             logd("isUsingNonTerrestrialNetwork: carrierEnabledSatelliteFlag is disabled");
             return new Pair<>(false, null);
@@ -2897,6 +3617,11 @@
         }
 
         int subId = phone.getSubId();
+        int carrierRoamingNtnConnectType = getCarrierRoamingNtnConnectType(subId);
+        if (carrierRoamingNtnConnectType == CARRIER_ROAMING_NTN_CONNECT_MANUAL) {
+            return isInCarrierRoamingNbIotNtn(phone);
+        }
+
         if (!isSatelliteSupportedViaCarrier(subId)) {
             return false;
         }
@@ -2910,8 +3635,9 @@
             return true;
         }
 
-        if (getWwanIsInService(serviceState)) {
-            // Device is connected to terrestrial network which has coverage
+        if (getWwanIsInService(serviceState)
+                || serviceState.getState() == ServiceState.STATE_POWER_OFF) {
+            // Device is connected to terrestrial network which has coverage or radio is turned off
             resetCarrierRoamingSatelliteModeParams(subId);
             return false;
         }
@@ -2934,6 +3660,81 @@
     }
 
     /**
+     * @return {@code true} if should exit satellite mode unless already sent a datagram in this
+     * 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 whether the satellite request is for an emergency or not.
+     *
+     * @return {@code true} if the satellite request is for an emergency and
+     *                      {@code false} otherwise.
+     */
+    public boolean getRequestIsEmergency() {
+        return mIsEmergency;
+    }
+
+    /**
+     * @return {@code true} if device is in carrier roaming nb iot ntn mode,
+     * else {@return false}
+     */
+    public boolean isInCarrierRoamingNbIotNtn() {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("isInCarrierRoamingNbIotNtn: carrier roaming nb iot ntn "
+                    + "feature flag is disabled");
+            return false;
+        }
+
+        if (!isSatelliteEnabled()) {
+            plogd("iisInCarrierRoamingNbIotNtn: satellite is disabled");
+            return false;
+        }
+
+        Phone satellitePhone = getSatellitePhone();
+        if (!isCarrierRoamingNtnEligible(satellitePhone)) {
+            plogd("isInCarrierRoamingNbIotNtn: not carrier roaming ntn eligible.");
+            return false;
+        }
+        plogd("isInCarrierRoamingNbIotNtn: carrier roaming ntn eligible.");
+        return true;
+    }
+
+    /**
+     * @return {@code true} if phone is in carrier roaming nb iot ntn mode,
+     * else {@return false}
+     */
+    public boolean isInCarrierRoamingNbIotNtn(@NonNull Phone phone) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("isInCarrierRoamingNbIotNtn: carrier roaming nb iot ntn "
+                    + "feature flag is disabled");
+            return false;
+        }
+
+        if (!isSatelliteEnabled()) {
+            plogd("iisInCarrierRoamingNbIotNtn: satellite is disabled");
+            return false;
+        }
+
+        if (!isCarrierRoamingNtnEligible(phone)) {
+            plogd("isInCarrierRoamingNbIotNtn: phone associated with subId "
+                      + phone.getSubId()
+                      + " is not carrier roaming ntn eligible.");
+            return false;
+        }
+        plogd("isInCarrierRoamingNbIotNtn: carrier roaming ntn eligible for phone"
+                  + " associated with subId " + phone.getSubId());
+        return true;
+    }
+
+    /**
      * Return capabilities of carrier roaming satellite network.
      *
      * @param phone phone object
@@ -3073,6 +3874,13 @@
             if (mSatelliteEntitlementStatusPerCarrier.get(subId, false) != entitlementEnabled) {
                 logd("update the carrier satellite enabled to " + entitlementEnabled);
                 mSatelliteEntitlementStatusPerCarrier.put(subId, entitlementEnabled);
+                mCarrierRoamingSatelliteControllerStats.reportIsDeviceEntitled(entitlementEnabled);
+                if (hasMessages(EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT)) {
+                    removeMessages(EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT);
+                    sendMessageDelayed(obtainMessage(
+                                    EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT),
+                            WAIT_FOR_REPORT_ENTITLED_MERTICS_TIMEOUT_MILLIS);
+                }
                 try {
                     mSubscriptionManagerService.setSubscriptionProperty(subId,
                             SATELLITE_ENTITLEMENT_STATUS, entitlementEnabled ? "1" : "0");
@@ -3132,7 +3940,7 @@
          * We have not successfully checked whether the modem supports satellite service.
          * Thus, we need to retry it now.
          */
-        requestIsSatelliteSupported(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+        requestIsSatelliteSupported(
                 new ResultReceiver(this) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -3156,6 +3964,7 @@
             mProvisionMetricsStats
                     .setResultCode(SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE)
                     .setIsProvisionRequest(true)
+                    .setCarrierId(getSatelliteCarrierId())
                     .reportProvisionMetrics();
             mControllerMetricsStats.reportProvisionCount(
                     SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
@@ -3164,8 +3973,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);
@@ -3174,6 +3983,7 @@
         }
         mProvisionMetricsStats.setResultCode(result)
                 .setIsProvisionRequest(true)
+                .setCarrierId(getSatelliteCarrierId())
                 .reportProvisionMetrics();
         mControllerMetricsStats.reportProvisionCount(result);
     }
@@ -3191,8 +4001,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);
@@ -3203,6 +4013,7 @@
         }
         mProvisionMetricsStats.setResultCode(result)
                 .setIsProvisionRequest(false)
+                .setCarrierId(getSatelliteCarrierId())
                 .reportProvisionMetrics();
         mControllerMetricsStats.reportDeprovisionCount(result);
     }
@@ -3241,84 +4052,65 @@
     }
 
     /**
-     * Posts the specified command to be executed on the main thread. As this is a synchronous
-     * request, it waits until the request is complete and then return the result.
-     *
-     * @param command command to be executed on the main thread
-     * @param argument additional parameters required to perform of the operation
-     * @param phone phone object used to perform the operation.
-     * @return result of the operation
-     */
-    private @Nullable Object sendRequest(int command, @NonNull Object argument,
-            @Nullable Phone phone) {
-        if (Looper.myLooper() == this.getLooper()) {
-            throw new RuntimeException("This method will deadlock if called from the main thread");
-        }
-
-        SatelliteControllerHandlerRequest request = new SatelliteControllerHandlerRequest(
-                argument, phone);
-        Message msg = this.obtainMessage(command, request);
-        msg.sendToTarget();
-
-        synchronized (request) {
-            while(request.result == null) {
-                try {
-                    request.wait();
-                } catch (InterruptedException e) {
-                    // Do nothing, go back and wait until the request is complete.
-                }
-            }
-        }
-        return request.result;
-    }
-
-    /**
-     * 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) {
-                return mIsSatelliteViaOemProvisioned;
+            if (mIsDeviceProvisioned == null) {
+                mIsDeviceProvisioned = getPersistedDeviceProvisionStatus();
             }
+            return mIsDeviceProvisioned;
         }
-
-        requestIsSatelliteProvisioned(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                new ResultReceiver(this) {
-                    @Override
-                    protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        plogd("isSatelliteViaOemProvisioned: resultCode=" + resultCode);
-                    }
-                });
-        return null;
     }
 
     private void handleSatelliteEnabled(SatelliteControllerHandlerRequest request) {
         RequestSatelliteEnabledArgument argument =
                 (RequestSatelliteEnabledArgument) request.argument;
         handlePersistentLoggingOnSessionStart(argument);
+        selectBindingSatelliteSubscription();
+        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 {
             ploge("handleSatelliteEnabled: mSatelliteSessionController is not initialized yet");
         }
 
+        /* Framework will send back the disable result to the requesting client only after receiving
+         * both confirmation for the disable request from modem, and OFF state from modem if the
+         * modem is not in OFF state.
+         */
         if (!argument.enableSatellite && mSatelliteModemInterface.isSatelliteServiceSupported()) {
-            synchronized (mIsSatelliteEnabledLock) {
+            synchronized (mSatelliteEnabledRequestLock) {
                 mWaitingForDisableSatelliteModemResponse = true;
-                mWaitingForSatelliteModemOff = true;
+                if (!isSatelliteDisabled()) mWaitingForSatelliteModemOff = true;
             }
         }
 
         Message onCompleted = obtainMessage(EVENT_SET_SATELLITE_ENABLED_DONE, request);
-        mSatelliteModemInterface.requestSatelliteEnabled(argument.enableSatellite,
-                argument.enableDemoMode, argument.isEmergency, onCompleted);
+        mSatelliteModemInterface.requestSatelliteEnabled(
+                enableRequestAttributes, onCompleted);
         startWaitForSatelliteEnablingResponseTimer(argument);
         // Logs satellite session timestamps for session metrics
         if (argument.enableSatellite) {
@@ -3327,6 +4119,22 @@
         mSessionProcessingTimeStamp = System.currentTimeMillis();
     }
 
+    /** Get the request attributes that modem needs to enable/disable satellite */
+    @Nullable private SatelliteModemEnableRequestAttributes createModemEnableRequest(
+            @NonNull RequestSatelliteEnabledArgument arg) {
+        int subId = getSelectedSatelliteSubId();
+        SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId);
+        if (subInfo == null) {
+            loge("createModemEnableRequest: no SubscriptionInfo found for subId=" + subId);
+            return null;
+        }
+        String iccid = subInfo.getIccId();
+        String apn = getConfigForSubId(subId).getString(KEY_SATELLITE_NIDD_APN_NAME_STRING, "");
+        return new SatelliteModemEnableRequestAttributes(
+                arg.enableSatellite, arg.enableDemoMode, arg.isEmergency,
+                new SatelliteSubscriptionInfo(iccid, apn));
+    }
+
     private void handleRequestSatelliteAttachRestrictionForCarrierCmd(
             SatelliteControllerHandlerRequest request) {
         RequestHandleSatelliteAttachRestrictionForCarrierArgument argument =
@@ -3342,48 +4150,50 @@
         evaluateEnablingSatelliteForCarrier(argument.subId, argument.reason, argument.callback);
     }
 
-    private void updateSatelliteSupportedStateWhenSatelliteServiceConnected(boolean supported) {
+    private void updateSatelliteSupportedState(boolean supported) {
         synchronized (mIsSatelliteSupportedLock) {
             mIsSatelliteSupported = supported;
         }
         mSatelliteSessionController = SatelliteSessionController.make(
                 mContext, getLooper(), mFeatureFlags, supported);
-        plogd("create a new SatelliteSessionController due to isSatelliteSupported state has "
-                + "changed to " + supported);
+        plogd("updateSatelliteSupportedState: create a new SatelliteSessionController because "
+                + "satellite supported state has changed to " + supported);
 
         if (supported) {
-            registerForSatelliteProvisionStateChanged();
             registerForPendingDatagramCount();
             registerForSatelliteModemStateChanged();
             registerForNtnSignalStrengthChanged();
             registerForCapabilitiesChanged();
+            registerForSatelliteRegistrationFailure();
+            registerForTerrestrialNetworkAvailableChanged();
 
-            requestIsSatelliteProvisioned(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+            requestIsSatelliteProvisioned(
                     new ResultReceiver(this) {
                         @Override
                         protected void onReceiveResult(int resultCode, Bundle resultData) {
-                            plogd("requestIsSatelliteProvisioned: resultCode=" + resultCode
-                                    + ", resultData=" + resultData);
-                            requestSatelliteEnabled(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                                    false, false, false,
+                            plogd("updateSatelliteSupportedState.requestIsSatelliteProvisioned: "
+                                    + "resultCode=" + resultCode + ", resultData=" + resultData);
+                            requestSatelliteEnabled(false, false, false,
                                     new IIntegerConsumer.Stub() {
                                         @Override
                                         public void accept(int result) {
-                                            plogd("requestSatelliteEnabled: result=" + result);
+                                            plogd("updateSatelliteSupportedState."
+                                                    + "requestSatelliteEnabled: result=" + result);
                                         }
                                     });
                         }
                     });
-            requestSatelliteCapabilities(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+            requestSatelliteCapabilities(
                     new ResultReceiver(this) {
                         @Override
                         protected void onReceiveResult(int resultCode, Bundle resultData) {
-                            plogd("requestSatelliteCapabilities: resultCode=" + resultCode
-                                    + ", resultData=" + resultData);
+                            plogd("updateSatelliteSupportedState.requestSatelliteCapabilities: "
+                                    + "resultCode=" + resultCode + ", resultData=" + resultData);
                         }
                     });
         }
         registerForSatelliteSupportedStateChanged();
+        selectBindingSatelliteSubscription();
     }
 
     private void updateSatelliteEnabledState(boolean enabled, String caller) {
@@ -3401,16 +4211,6 @@
         }
     }
 
-    private void registerForSatelliteProvisionStateChanged() {
-        if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
-            if (!mRegisteredForProvisionStateChangedWithSatelliteService.get()) {
-                mSatelliteModemInterface.registerForSatelliteProvisionStateChanged(
-                        this, EVENT_SATELLITE_PROVISION_STATE_CHANGED, null);
-                mRegisteredForProvisionStateChangedWithSatelliteService.set(true);
-            }
-        }
-    }
-
     private void registerForPendingDatagramCount() {
         if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
             if (!mRegisteredForPendingDatagramCountWithSatelliteService.get()) {
@@ -3471,14 +4271,37 @@
         }
     }
 
+    private void registerForSatelliteRegistrationFailure() {
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            if (!mRegisteredForSatelliteRegistrationFailure.get()) {
+                mSatelliteModemInterface.registerForSatelliteRegistrationFailure(this,
+                        EVENT_SATELLITE_REGISTRATION_FAILURE, null);
+                mRegisteredForSatelliteRegistrationFailure.set(true);
+            }
+        }
+    }
+
+    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 {
@@ -3493,42 +4316,122 @@
         });
     }
 
+    private boolean updateSatelliteSubscriptionProvisionState(List<SatelliteSubscriberInfo> newList,
+            boolean provisioned) {
+        logd("updateSatelliteSubscriptionProvisionState: List=" + newList + " , provisioned="
+                + provisioned);
+        boolean provisionChanged = false;
+        synchronized (mSatelliteTokenProvisionedLock) {
+            for (SatelliteSubscriberInfo subscriberInfo : newList) {
+                Boolean currentProvisioned =
+                        mProvisionedSubscriberId.get(subscriberInfo.getSubscriberId());
+                if (currentProvisioned != null && currentProvisioned == provisioned) {
+                    continue;
+                }
+                provisionChanged = true;
+                mProvisionedSubscriberId.put(subscriberInfo.getSubscriberId(), provisioned);
+                int subId = subscriberInfo.getSubId();
+                try {
+                    mSubscriptionManagerService.setIsSatelliteProvisionedForNonIpDatagram(subId,
+                            provisioned);
+                    plogd("updateSatelliteSubscriptionProvisionState: set Provision state to db "
+                            + "subId=" + subId);
+                } catch (IllegalArgumentException | SecurityException ex) {
+                    ploge("setIsSatelliteProvisionedForNonIpDatagram: subId=" + subId + ", ex="
+                            + ex);
+                }
+            }
+        }
+        return provisionChanged;
+    }
+
+    private void handleEventSatelliteSubscriptionProvisionStateChanged() {
+        List<SatelliteSubscriberProvisionStatus> informList =
+                getPrioritizedSatelliteSubscriberProvisionStatusList();
+        plogd("handleEventSatelliteSubscriptionProvisionStateChanged: " + informList);
+        notifySatelliteSubscriptionProvisionStateChanged(informList);
+        updateDeviceProvisionStatus();
+        // Report updated provisioned status to metrics.
+        synchronized (mSatelliteTokenProvisionedLock) {
+            boolean isProvisioned = !mProvisionedSubscriberId.isEmpty()
+                    && mProvisionedSubscriberId.containsValue(Boolean.TRUE);
+            mControllerMetricsStats.setIsProvisioned(isProvisioned);
+        }
+        selectBindingSatelliteSubscription();
+        handleStateChangedForCarrierRoamingNtnEligibility();
+    }
+
+    private void updateDeviceProvisionStatus() {
+        boolean isProvisioned = getPersistedDeviceProvisionStatus();
+        plogd("updateDeviceProvisionStatus: isProvisioned=" + isProvisioned);
+        synchronized (mDeviceProvisionLock) {
+            if (mIsDeviceProvisioned == null || mIsDeviceProvisioned != isProvisioned) {
+                mIsDeviceProvisioned = isProvisioned;
+                notifyDeviceProvisionStateChanged(isProvisioned);
+            }
+        }
+    }
+
+    private void notifySatelliteSubscriptionProvisionStateChanged(
+            @NonNull List<SatelliteSubscriberProvisionStatus> list) {
+        List<ISatelliteProvisionStateCallback> deadCallersList = new ArrayList<>();
+        mSatelliteProvisionStateChangedListeners.values().forEach(listener -> {
+            try {
+                listener.onSatelliteSubscriptionProvisionStateChanged(list);
+            } catch (RemoteException e) {
+                plogd("notifySatelliteSubscriptionProvisionStateChanged: " + e);
+                deadCallersList.add(listener);
+            }
+        });
+        deadCallersList.forEach(listener -> {
+            mSatelliteProvisionStateChangedListeners.remove(listener.asBinder());
+        });
+    }
+
     private void handleEventSatelliteModemStateChanged(
             @SatelliteManager.SatelliteModemState int state) {
         plogd("handleEventSatelliteModemStateChanged: state=" + state);
         if (state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE
                 || state == SatelliteManager.SATELLITE_MODEM_STATE_OFF) {
-            synchronized (mIsSatelliteEnabledLock) {
-                if ((state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE)
-                        || ((mIsSatelliteEnabled == null || isSatelliteEnabled())
-                        && !mWaitingForDisableSatelliteModemResponse)) {
-                    int error = (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF)
-                            ? SATELLITE_RESULT_SUCCESS
-                            : SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE;
-                    Consumer<Integer> callback = null;
-                    synchronized (mSatelliteEnabledRequestLock) {
-                        if (mSatelliteEnabledRequest != null) {
-                            callback = mSatelliteEnabledRequest.callback;
-                        }
-                    }
-                    moveSatelliteToOffStateAndCleanUpResources(error, callback);
+            synchronized (mSatelliteEnabledRequestLock) {
+                if (!mWaitingForDisableSatelliteModemResponse) {
+                    moveSatelliteToOffStateAndCleanUpResources(
+                            SATELLITE_RESULT_SUCCESS);
                 } else {
-                    plogd("Either waiting for the response of disabling satellite modem or the"
-                            + " event should be ignored because isSatelliteEnabled="
-                            + isSatelliteEnabled()
-                            + ", mIsSatelliteEnabled=" + mIsSatelliteEnabled);
+                    notifyModemStateChangedToSessionController(
+                            SatelliteManager.SATELLITE_MODEM_STATE_OFF);
                 }
                 mWaitingForSatelliteModemOff = false;
             }
         } else {
-            if (mSatelliteSessionController != null) {
-                mSatelliteSessionController.onSatelliteModemStateChanged(state);
+            if (isSatelliteEnabledOrBeingEnabled() || isSatelliteBeingDisabled()) {
+                notifyModemStateChangedToSessionController(state);
             } else {
-                ploge("handleEventSatelliteModemStateChanged: mSatelliteSessionController is null");
+                // Telephony framework and modem are out of sync. We need to disable modem
+                synchronized (mSatelliteEnabledRequestLock) {
+                    plogw("Satellite modem is in a bad state. Disabling satellite modem now ...");
+                    Consumer<Integer> result = integer -> plogd(
+                            "handleEventSatelliteModemStateChanged: disabling satellite result="
+                            + integer);
+                    mSatelliteDisabledRequest = new RequestSatelliteEnabledArgument(
+                            false /* enableSatellite */, false /* enableDemoMode */,
+                            false /* isEmergency */, result);
+                    sendRequestAsync(CMD_SET_SATELLITE_ENABLED, mSatelliteDisabledRequest, null);
+                }
             }
         }
     }
 
+    private void notifyModemStateChangedToSessionController(
+            @SatelliteManager.SatelliteModemState int state) {
+        if (mSatelliteSessionController != null) {
+            mSatelliteSessionController.onSatelliteModemStateChanged(state);
+        } else {
+            ploge("notifyModemStateChangedToSessionController: mSatelliteSessionController is "
+                    + "null");
+        }
+    }
+
     private void handleEventNtnSignalStrengthChanged(NtnSignalStrength ntnSignalStrength) {
         logd("handleEventNtnSignalStrengthChanged: ntnSignalStrength=" + ntnSignalStrength);
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
@@ -3568,8 +4471,11 @@
 
         List<ISatelliteCapabilitiesCallback> deadCallersList = new ArrayList<>();
         mSatelliteCapabilitiesChangedListeners.values().forEach(listener -> {
+            synchronized (this.mSatelliteCapabilitiesLock) {
+                overrideSatelliteCapabilitiesIfApplicable();
+            }
             try {
-                listener.onSatelliteCapabilitiesChanged(capabilities);
+                listener.onSatelliteCapabilitiesChanged(this.mSatelliteCapabilities);
             } catch (RemoteException e) {
                 plogd("handleEventSatelliteCapabilitiesChanged RemoteException: " + e);
                 deadCallersList.add(listener);
@@ -3592,7 +4498,7 @@
                 return;
             }
 
-            updateSatelliteSupportedStateWhenSatelliteServiceConnected(supported);
+            updateSatelliteSupportedState(supported);
 
             /* In case satellite has been reported as not support from modem, but satellite is
                enabled, request disable satellite. */
@@ -3600,15 +4506,13 @@
                 if (!supported && mIsSatelliteEnabled != null && mIsSatelliteEnabled) {
                     plogd("Invoke requestSatelliteEnabled(), supported=false, "
                             + "mIsSatelliteEnabled=true");
-                    requestSatelliteEnabled(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                            false /* enableSatellite */, false /* enableDemoMode */,
+                    requestSatelliteEnabled(false /* enableSatellite */, false /* enableDemoMode */,
                             false /* isEmergency */,
                             new IIntegerConsumer.Stub() {
                                 @Override
                                 public void accept(int result) {
                                     plogd("handleSatelliteSupportedStateChangedEvent: request "
-                                            + "satellite disable, result="
-                                            + result);
+                                            + "satellite disable, result=" + result);
                                 }
                             });
 
@@ -3653,7 +4557,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;
         }
 
@@ -3694,10 +4599,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;
+        }
     }
 
     /**
@@ -3762,8 +4676,6 @@
             if (areAllRadiosDisabled() && (mSatelliteEnabledRequest != null)
                     && mWaitingForRadioDisabled) {
                 plogd("Sending success to callback that sent enable satellite request");
-                setDemoModeEnabled(mSatelliteEnabledRequest.enableDemoMode);
-                mIsEmergency = mSatelliteEnabledRequest.isEmergency;
                 synchronized (mIsSatelliteEnabledLock) {
                     mIsSatelliteEnabled = mSatelliteEnabledRequest.enableSatellite;
                 }
@@ -3771,8 +4683,24 @@
                 updateSatelliteEnabledState(
                         mSatelliteEnabledRequest.enableSatellite,
                         "EVENT_SET_SATELLITE_ENABLED_DONE");
+                setEmergencyMode(mSatelliteEnabledRequest.isEmergency);
+                if (mSatelliteEnabledRequest.enableSatellite
+                        && !mSatelliteEnabledRequest.isEmergency) {
+                    plogd("Starting pointingUI needFullscreenPointingUI=" + true
+                            + "mIsDemoModeEnabled=" + mIsDemoModeEnabled + ", isEmergency="
+                            + mSatelliteEnabledRequest.isEmergency);
+                    mPointingAppController.startPointingUI(true, mIsDemoModeEnabled, false);
+                }
                 mSatelliteEnabledRequest = null;
                 mWaitingForRadioDisabled = false;
+
+                if (mSatelliteEnableAttributesUpdateRequest != null) {
+                    sendRequestAsync(CMD_UPDATE_SATELLITE_ENABLE_ATTRIBUTES,
+                            mSatelliteEnableAttributesUpdateRequest, null);
+                }
+                synchronized (mSatellitePhoneLock) {
+                    updateLastNotifiedNtnModeAndNotify(mSatellitePhone);
+                }
             }
         }
     }
@@ -3785,21 +4713,44 @@
         }
     }
 
-    private void moveSatelliteToOffStateAndCleanUpResources(
-            @SatelliteManager.SatelliteResult int error, @Nullable Consumer<Integer> callback) {
+    private void resetSatelliteDisabledRequest() {
+        plogd("resetSatelliteDisabledRequest");
+        synchronized (mSatelliteEnabledRequestLock) {
+            mSatelliteDisabledRequest = null;
+            mWaitingForDisableSatelliteModemResponse = false;
+            mWaitingForSatelliteModemOff = false;
+        }
+    }
+
+    /**
+     * Move to OFF state and clean up resources.
+     *
+     * @param resultCode The result code will be returned to requesting clients.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void moveSatelliteToOffStateAndCleanUpResources(
+            @SatelliteManager.SatelliteResult int resultCode) {
         plogd("moveSatelliteToOffStateAndCleanUpResources");
         synchronized (mIsSatelliteEnabledLock) {
-            resetSatelliteEnabledRequest();
             setDemoModeEnabled(false);
             handlePersistentLoggingOnSessionEnd(mIsEmergency);
-            mIsEmergency = false;
+            setEmergencyMode(false);
             mIsSatelliteEnabled = false;
             setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_FALSE);
             setSettingsKeyToAllowDeviceRotation(SATELLITE_MODE_ENABLED_FALSE);
-            if (callback != null) callback.accept(error);
+            abortSatelliteDisableRequest(resultCode);
+            abortSatelliteEnableRequest(resultCode);
+            abortSatelliteEnableAttributesUpdateRequest(resultCode);
+            resetSatelliteEnabledRequest();
+            resetSatelliteDisabledRequest();
+            // TODO (b/361139260): Stop timer to wait for other radios off
             updateSatelliteEnabledState(
                     false, "moveSatelliteToOffStateAndCleanUpResources");
         }
+        selectBindingSatelliteSubscription();
+        synchronized (mSatellitePhoneLock) {
+            updateLastNotifiedNtnModeAndNotify(mSatellitePhone);
+        }
     }
 
     private void setDemoModeEnabled(boolean enabled) {
@@ -3808,6 +4759,18 @@
         plogd("setDemoModeEnabled: mIsDemoModeEnabled=" + mIsDemoModeEnabled);
     }
 
+    private void setEmergencyMode(boolean isEmergency) {
+        plogd("setEmergencyMode: mIsEmergency=" + mIsEmergency + ", isEmergency=" + isEmergency);
+        if (mIsEmergency != isEmergency) {
+            mIsEmergency = isEmergency;
+            if (mSatelliteSessionController != null) {
+                mSatelliteSessionController.onEmergencyModeChanged(mIsEmergency);
+            } else {
+                plogw("setEmergencyMode: mSatelliteSessionController is null");
+            }
+        }
+    }
+
     private boolean isMockModemAllowed() {
         return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false));
     }
@@ -3850,6 +4813,7 @@
             if (activeSubIds != null) {
                 for (int subId : activeSubIds) {
                     updateSupportedSatelliteServices(subId);
+                    handleCarrierRoamingNtnAvailableServicesChanged(subId);
                 }
             } else {
                 loge("updateSupportedSatelliteServicesForActiveSubscriptions: "
@@ -3963,14 +4927,32 @@
     }
 
     @NonNull private PersistableBundle getConfigForSubId(int subId) {
-        PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId,
-                KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
-                KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
-                KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT,
-                KEY_SATELLITE_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);
+        PersistableBundle config = null;
+        if (mCarrierConfigManager != null) {
+            try {
+                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
+                );
+            } catch (Exception e) {
+                logw("getConfigForSubId: " + e);
+            }
+        }
         if (config == null || config.isEmpty()) {
             config = CarrierConfigManager.getDefaultConfig();
         }
@@ -3987,10 +4969,21 @@
         }
 
         updateCarrierConfig(subId);
+        updateSatelliteESOSSupported(subId);
+        updateSatelliteProvisionedStatePerSubscriberId();
         updateEntitlementPlmnListPerCarrier(subId);
         updateSupportedSatelliteServicesForActiveSubscriptions();
         processNewCarrierConfigData(subId);
         resetCarrierRoamingSatelliteModeParams(subId);
+        handleStateChangedForCarrierRoamingNtnEligibility();
+        sendMessageDelayed(obtainMessage(CMD_EVALUATE_ESOS_PROFILES_PRIORITIZATION),
+                mEvaluateEsosProfilesPrioritizationDurationMillis);
+    }
+
+    // imsi, msisdn, default sms subId change
+    private void handleSubscriptionsChanged() {
+        sendMessageDelayed(obtainMessage(CMD_EVALUATE_ESOS_PROFILES_PRIORITIZATION),
+                mEvaluateEsosProfilesPrioritizationDurationMillis);
     }
 
     private void processNewCarrierConfigData(int subId) {
@@ -4050,6 +5043,56 @@
         }
     }
 
+    /**
+     * Update the value of SimInfo.COLUMN_SATELLITE_ESOS_SUPPORTED stored in the database based
+     * on the value in the carrier config.
+     */
+    private void updateSatelliteESOSSupported(int subId) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            return;
+        }
+
+        boolean isSatelliteESosSupportedFromDB =
+                mSubscriptionManagerService.getSatelliteESOSSupported(subId);
+        boolean isSatelliteESosSupportedFromCarrierConfig = getConfigForSubId(subId).getBoolean(
+                KEY_SATELLITE_ESOS_SUPPORTED_BOOL, false);
+        if (isSatelliteESosSupportedFromDB != isSatelliteESosSupportedFromCarrierConfig) {
+            mSubscriptionManagerService.setSatelliteESOSSupported(subId,
+                    isSatelliteESosSupportedFromCarrierConfig);
+            logd("updateSatelliteESOSSupported: " + isSatelliteESosSupportedFromCarrierConfig);
+        }
+    }
+
+    /** 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() {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            return;
+        }
+
+        List<SubscriptionInfo> allSubInfos = mSubscriptionManagerService.getAllSubInfoList(
+                mContext.getOpPackageName(), mContext.getAttributionTag());
+        for (SubscriptionInfo info : allSubInfos) {
+            int subId = info.getSubscriptionId();
+            Pair<String, Integer> subscriberIdPair = getSubscriberIdAndType(
+                    mSubscriptionManagerService.getSubscriptionInfo(subId));
+            String subscriberId = subscriberIdPair.first;
+            synchronized (mSatelliteTokenProvisionedLock) {
+                if (mProvisionedSubscriberId.get(subscriberId) == null) {
+                    boolean Provisioned = mSubscriptionManagerService
+                            .isSatelliteProvisionedForNonIpDatagram(subId);
+                    if (Provisioned) {
+                        mProvisionedSubscriberId.put(subscriberId, true);
+                        logd("updateSatelliteProvisionStatePerSubscriberId: " + subscriberId
+                                + " set true");
+                    }
+                }
+            }
+        }
+        // Need to update the provision status of the device
+        updateDeviceProvisionStatus();
+    }
+
     @NonNull
     private String[] readStringArrayFromOverlayConfig(@ArrayRes int id) {
         String[] strArray = null;
@@ -4070,6 +5113,46 @@
     }
 
     /**
+     * Return whether the device support P2P SMS mode from carrier config.
+     *
+     * @param subId Associated subscription ID
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean isSatelliteRoamingP2pSmSSupported(int subId) {
+        return getConfigForSubId(subId).getBoolean(KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL);
+    }
+
+    /**
+     * Return whether the device support ESOS mode from carrier config.
+     *
+     * @param subId Associated subscription ID
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean isSatelliteEsosSupported(int subId) {
+        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);
+    }
+
+    protected int getCarrierRoamingNtnEmergencyCallToSatelliteHandoverType(int subId) {
+        return getConfigForSubId(subId).getInt(
+                KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT);
+    }
+
+    /**
      * Check if satellite attach is enabled by user for the carrier associated with the
      * {@code subId}.
      *
@@ -4176,6 +5259,13 @@
                 }
                 boolean result = entitlementStatus.equals("1");
                 mSatelliteEntitlementStatusPerCarrier.put(subId, result);
+                mCarrierRoamingSatelliteControllerStats.reportIsDeviceEntitled(result);
+                if (hasMessages(EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT)) {
+                    removeMessages(EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT);
+                    sendMessageDelayed(obtainMessage(
+                                    EVENT_WAIT_FOR_REPORT_ENTITLED_TO_MERTICS_HYSTERESIS_TIMED_OUT),
+                            WAIT_FOR_REPORT_ENTITLED_MERTICS_TIMEOUT_MILLIS);
+                }
             }
 
             if (!mSatelliteEntitlementStatusPerCarrier.get(subId, false)) {
@@ -4314,12 +5404,13 @@
         }
 
         if (isProvisionRequired) {
-            Boolean satelliteProvisioned = isSatelliteViaOemProvisioned();
+            Boolean satelliteProvisioned = isDeviceProvisioned();
             if (satelliteProvisioned == null) {
                 plogd("evaluateOemSatelliteRequestAllowed: satelliteProvisioned is null");
                 return SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
             }
             if (!satelliteProvisioned) {
+                plogd("evaluateOemSatelliteRequestAllowed: satellite service is not provisioned");
                 return SatelliteManager.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED;
             }
         }
@@ -4348,6 +5439,7 @@
         mSessionMetricsStats.setInitializationResult(error)
                 .setSatelliteTechnology(getSupportedNtnRadioTechnology())
                 .setIsDemoMode(mIsDemoModeEnabled)
+                .setCarrierId(getSatelliteCarrierId())
                 .reportSessionMetrics();
         mSessionStartTimeStamp = 0;
         mSessionProcessingTimeStamp = 0;
@@ -4363,22 +5455,26 @@
     }
 
     private void handleEventServiceStateChanged() {
+        handleStateChangedForCarrierRoamingNtnEligibility();
         handleServiceStateForSatelliteConnectionViaCarrier();
-        determineSystemNotification();
     }
 
     private void handleServiceStateForSatelliteConnectionViaCarrier() {
         for (Phone phone : PhoneFactory.getPhones()) {
             int subId = phone.getSubId();
             ServiceState serviceState = phone.getServiceState();
-            if (serviceState == null) {
+            if (serviceState == null || subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
                 continue;
             }
 
             synchronized (mSatelliteConnectedLock) {
                 CarrierRoamingSatelliteSessionStats sessionStats =
                         mCarrierRoamingSatelliteSessionStatsMap.get(subId);
-
+                if (DEBUG) {
+                    plogd("handleServiceStateForSatelliteConnectionViaCarrier : SubId = " + subId
+                            + "  isUsingNonTerrestrialNetwork = "
+                            + serviceState.isUsingNonTerrestrialNetwork());
+                }
                 if (serviceState.isUsingNonTerrestrialNetwork()) {
                     if (sessionStats != null) {
                         sessionStats.onSignalStrength(phone);
@@ -4400,7 +5496,8 @@
                     }
                 } else {
                     Boolean connected = mWasSatelliteConnectedViaCarrier.get(subId);
-                    if (getWwanIsInService(serviceState)) {
+                    if (getWwanIsInService(serviceState)
+                            || serviceState.getState() == ServiceState.STATE_POWER_OFF) {
                         resetCarrierRoamingSatelliteModeParams(subId);
                     } else if (connected != null && connected) {
                         // The device just got disconnected from a satellite network
@@ -4424,11 +5521,11 @@
                 updateLastNotifiedNtnModeAndNotify(phone);
             }
         }
+        determineAutoConnectSystemNotification();
     }
 
     private void updateLastNotifiedNtnModeAndNotify(@Nullable Phone phone) {
         if (!mFeatureFlags.carrierEnabledSatelliteFlag()) return;
-
         if (phone == null) {
             return;
         }
@@ -4443,6 +5540,9 @@
                 mLastNotifiedNtnMode.put(subId, currNtnMode);
                 phone.notifyCarrierRoamingNtnModeChanged(currNtnMode);
                 logCarrierRoamingSatelliteSessionStats(phone, lastNotifiedNtnMode, currNtnMode);
+                if(mIsNotificationShowing && !currNtnMode) {
+                    dismissSatelliteNotification();
+                }
             }
         }
     }
@@ -4467,37 +5567,179 @@
         }
     }
 
+    private void handleStateChangedForCarrierRoamingNtnEligibility() {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("handleStateChangedForCarrierRoamingNtnEligibility: "
+                    + "carrierRoamingNbIotNtn flag is disabled");
+            return;
+        }
+
+        boolean eligible = isCarrierRoamingNtnEligible(mSatellitePhone);
+        plogd("handleStateChangedForCarrierRoamingNtnEligibility: "
+                + "isCarrierRoamingNtnEligible=" + eligible);
+
+        synchronized (mSatellitePhoneLock) {
+            if (eligible) {
+                if (shouldStartNtnEligibilityHysteresisTimer(eligible)) {
+                    startNtnEligibilityHysteresisTimer();
+                }
+            } else {
+                mNtnEligibilityHysteresisTimedOut = false;
+                stopNtnEligibilityHysteresisTimer();
+                updateLastNotifiedNtnEligibilityAndNotify(false);
+            }
+        }
+    }
+
+    private boolean shouldStartNtnEligibilityHysteresisTimer(boolean eligible) {
+        if (!eligible) {
+            return false;
+        }
+
+        if (hasMessages(EVENT_NOTIFY_NTN_ELIGIBILITY_HYSTERESIS_TIMED_OUT)) {
+            plogd("shouldStartNtnEligibilityHysteresisTimer: Timer is already running.");
+            return false;
+        }
+
+        synchronized (mSatellitePhoneLock) {
+            if (mLastNotifiedNtnEligibility != null && mLastNotifiedNtnEligibility) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private void startNtnEligibilityHysteresisTimer() {
+        synchronized (mSatellitePhoneLock) {
+            if (mSatellitePhone == null) {
+                ploge("startNtnEligibilityHysteresisTimer: mSatellitePhone is null.");
+                return;
+            }
+
+            int subId = getSelectedSatelliteSubId();
+            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);
+        }
+    }
+
+    private void stopNtnEligibilityHysteresisTimer() {
+        if (hasMessages(EVENT_NOTIFY_NTN_ELIGIBILITY_HYSTERESIS_TIMED_OUT)) {
+            removeMessages(EVENT_NOTIFY_NTN_ELIGIBILITY_HYSTERESIS_TIMED_OUT);
+        }
+    }
+
+    private void updateLastNotifiedNtnEligibilityAndNotify(boolean currentNtnEligibility) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("notifyNtnEligibility: carrierRoamingNbIotNtn flag is disabled");
+            return;
+        }
+
+        if (mOverrideNtnEligibility != null) {
+            mSatellitePhone.notifyCarrierRoamingNtnEligibleStateChanged(currentNtnEligibility);
+            return;
+        }
+
+        synchronized (mSatellitePhoneLock) {
+            if (mSatellitePhone == null) {
+                ploge("notifyNtnEligibility: mSatellitePhone is null");
+                return;
+            }
+
+            plogd("notifyNtnEligibility: phoneId=" + mSatellitePhone.getPhoneId()
+                    + " currentNtnEligibility=" + currentNtnEligibility);
+            if (mLastNotifiedNtnEligibility == null
+                    || mLastNotifiedNtnEligibility != currentNtnEligibility) {
+                mLastNotifiedNtnEligibility = currentNtnEligibility;
+                mSatellitePhone.notifyCarrierRoamingNtnEligibleStateChanged(currentNtnEligibility);
+                updateSatelliteSystemNotification(getSelectedSatelliteSubId(),
+                        CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_MANUAL,
+                        currentNtnEligibility);
+            }
+        }
+    }
+
     private long getSatelliteConnectionHysteresisTimeMillis(int subId) {
         PersistableBundle config = getPersistableBundle(subId);
         return (config.getInt(
                 KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT) * 1000L);
     }
 
+    private long getCarrierSupportedSatelliteNotificationHysteresisTimeMillis(int subId) {
+        PersistableBundle config = getPersistableBundle(subId);
+        return (config.getInt(
+                KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT) * 1000L);
+    }
+
     private void persistOemEnabledSatelliteProvisionStatus(boolean isProvisioned) {
-        synchronized (mSatelliteViaOemProvisionLock) {
+        synchronized (mDeviceProvisionLock) {
             plogd("persistOemEnabledSatelliteProvisionStatus: isProvisioned=" + isProvisioned);
-
-            if (!loadSatelliteSharedPreferences()) return;
-
-            if (mSharedPreferences == null) {
-                ploge("persistOemEnabledSatelliteProvisionStatus: mSharedPreferences is null");
+            if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+                int subId = getNtnOnlySubscriptionId();
+                if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                    try {
+                        mSubscriptionManagerService.setIsSatelliteProvisionedForNonIpDatagram(subId,
+                                isProvisioned);
+                        plogd("persistOemEnabledSatelliteProvisionStatus: subId=" + subId);
+                    } catch (IllegalArgumentException | SecurityException ex) {
+                        ploge("setIsSatelliteProvisionedForNonIpDatagram: subId=" + subId + ", ex="
+                                + ex);
+                    }
+                } else {
+                    plogd("persistOemEnabledSatelliteProvisionStatus: INVALID_SUBSCRIPTION_ID");
+                }
             } else {
-                mSharedPreferences.edit().putBoolean(
-                        OEM_ENABLED_SATELLITE_PROVISION_STATUS_KEY, isProvisioned).apply();
+                if (!loadSatelliteSharedPreferences()) return;
+
+                if (mSharedPreferences == null) {
+                    ploge("persistOemEnabledSatelliteProvisionStatus: mSharedPreferences is null");
+                } else {
+                    mSharedPreferences.edit().putBoolean(
+                            OEM_ENABLED_SATELLITE_PROVISION_STATUS_KEY, isProvisioned).apply();
+                }
             }
         }
     }
 
-    private boolean getPersistedOemEnabledSatelliteProvisionStatus() {
-        synchronized (mSatelliteViaOemProvisionLock) {
-            if (!loadSatelliteSharedPreferences()) return false;
+    @Nullable
+    private boolean getPersistedDeviceProvisionStatus() {
+        plogd("getPersistedDeviceProvisionStatus");
+        synchronized (mDeviceProvisionLock) {
+            if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+                int subId = getNtnOnlySubscriptionId();
+                if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                    if (mSubscriptionManagerService.isSatelliteProvisionedForNonIpDatagram(subId)) {
+                        return true;
+                    }
+                }
 
-            if (mSharedPreferences == null) {
-                ploge("getPersistedOemEnabledSatelliteProvisionStatus: mSharedPreferences is null");
+                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 {
-                return mSharedPreferences.getBoolean(
-                        OEM_ENABLED_SATELLITE_PROVISION_STATUS_KEY, false);
+                if (!loadSatelliteSharedPreferences()) return false;
+
+                if (mSharedPreferences == null) {
+                    ploge("getPersistedDeviceProvisionStatus: mSharedPreferences is "
+                            + "null");
+                    return false;
+                } else {
+                    return mSharedPreferences.getBoolean(
+                            OEM_ENABLED_SATELLITE_PROVISION_STATUS_KEY, false);
+                }
             }
         }
     }
@@ -4518,31 +5760,13 @@
     }
 
     private void handleIsSatelliteProvisionedDoneEvent(@NonNull AsyncResult ar) {
+        logd("handleIsSatelliteProvisionedDoneEvent:");
         SatelliteControllerHandlerRequest request = (SatelliteControllerHandlerRequest) ar.userObj;
-        int error = SatelliteServiceUtils.getSatelliteError(
-                ar, "handleIsSatelliteProvisionedDoneEvent");
-        boolean isSatelliteProvisionedInModem = false;
-        if (error == SATELLITE_RESULT_SUCCESS) {
-            if (ar.result == null) {
-                ploge("handleIsSatelliteProvisionedDoneEvent: result is null");
-                error = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
-            } else {
-                isSatelliteProvisionedInModem = ((int[]) ar.result)[0] == 1;
-            }
-        } else if (error == SATELLITE_RESULT_REQUEST_NOT_SUPPORTED) {
-            plogd("handleIsSatelliteProvisionedDoneEvent: Modem does not support this request");
-            isSatelliteProvisionedInModem = true;
-        }
-        boolean isSatelliteViaOemProvisioned =
-                isSatelliteProvisionedInModem && getPersistedOemEnabledSatelliteProvisionStatus();
-        plogd("isSatelliteProvisionedInModem=" + isSatelliteProvisionedInModem
-                + ", isSatelliteViaOemProvisioned=" + isSatelliteViaOemProvisioned);
+
         Bundle bundle = new Bundle();
-        bundle.putBoolean(SatelliteManager.KEY_SATELLITE_PROVISIONED, isSatelliteViaOemProvisioned);
-        synchronized (mSatelliteViaOemProvisionLock) {
-            mIsSatelliteViaOemProvisioned = isSatelliteViaOemProvisioned;
-        }
-        ((ResultReceiver) request.argument).send(error, bundle);
+        bundle.putBoolean(SatelliteManager.KEY_SATELLITE_PROVISIONED,
+                Boolean.TRUE.equals(isDeviceProvisioned()));
+        ((ResultReceiver) request.argument).send(SATELLITE_RESULT_SUCCESS, bundle);
     }
 
     private long getWaitForSatelliteEnablingResponseTimeoutMillis() {
@@ -4550,6 +5774,32 @@
                 R.integer.config_wait_for_satellite_enabling_response_timeout_millis);
     }
 
+    private long getWaitForCellularModemOffTimeoutMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_satellite_wait_for_cellular_modem_off_timeout_millis);
+    }
+
+    private void startWaitForCellularModemOffTimer() {
+        synchronized (mIsRadioOnLock) {
+            if (hasMessages(EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT)) {
+                plogd("startWaitForCellularModemOffTimer: the timer was already started");
+                return;
+            }
+            long timeoutMillis = getWaitForCellularModemOffTimeoutMillis();
+            plogd("Start timer to wait for cellular modem OFF state, timeoutMillis="
+                    + timeoutMillis);
+            sendMessageDelayed(obtainMessage(EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT),
+                    timeoutMillis);
+        }
+    }
+
+    private void stopWaitForCellularModemOffTimer() {
+        synchronized (mSatelliteEnabledRequestLock) {
+            plogd("Stop timer to wait for cellular modem OFF state");
+            removeMessages(EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT);
+        }
+    }
+
     private void startWaitForSatelliteEnablingResponseTimer(
             @NonNull RequestSatelliteEnabledArgument argument) {
         synchronized (mSatelliteEnabledRequestLock) {
@@ -4586,78 +5836,96 @@
         }
     }
 
+    private void startWaitForUpdateSatelliteEnableAttributesResponseTimer(
+            @NonNull RequestSatelliteEnabledArgument argument) {
+        synchronized (mSatelliteEnabledRequestLock) {
+            if (hasMessages(EVENT_WAIT_FOR_UPDATE_SATELLITE_ENABLE_ATTRIBUTES_RESPONSE_TIMED_OUT,
+                    argument)) {
+                plogd("WaitForUpdateSatelliteEnableAttributesResponseTimer of request ID "
+                        + argument.requestId + " was already started");
+                return;
+            }
+            plogd("Start timer to wait for response of the update satellite enable attributes"
+                    + " request ID=" + argument.requestId
+                    + ", enableSatellite=" + argument.enableSatellite
+                    + ", mWaitTimeForSatelliteEnablingResponse="
+                    + mWaitTimeForSatelliteEnablingResponse);
+            sendMessageDelayed(obtainMessage(
+                    EVENT_WAIT_FOR_UPDATE_SATELLITE_ENABLE_ATTRIBUTES_RESPONSE_TIMED_OUT,
+                    argument), mWaitTimeForSatelliteEnablingResponse);
+        }
+    }
+
+    private void stopWaitForUpdateSatelliteEnableAttributesResponseTimer(
+            @NonNull RequestSatelliteEnabledArgument argument) {
+        synchronized (mSatelliteEnabledRequestLock) {
+            plogd("Stop timer to wait for response of the enable attributes update request ID="
+                    + argument.requestId + ", enableSatellite=" + argument.enableSatellite);
+            removeMessages(
+                    EVENT_WAIT_FOR_UPDATE_SATELLITE_ENABLE_ATTRIBUTES_RESPONSE_TIMED_OUT, argument);
+        }
+    }
+
+    private boolean shouldProcessEventUpdateSatelliteEnableAttributesDone(
+            @NonNull RequestSatelliteEnabledArgument argument) {
+        synchronized (mSatelliteEnabledRequestLock) {
+            if (hasMessages(EVENT_WAIT_FOR_UPDATE_SATELLITE_ENABLE_ATTRIBUTES_RESPONSE_TIMED_OUT,
+                    argument)) {
+                return true;
+            }
+            return false;
+        }
+    }
+
     private void handleEventWaitForSatelliteEnablingResponseTimedOut(
             @NonNull RequestSatelliteEnabledArgument argument) {
         plogw("Timed out to wait for response of the satellite enabling request ID="
                 + argument.requestId + ", enableSatellite=" + argument.enableSatellite);
 
-        synchronized (mSatelliteEnabledRequestLock) {
-            if (mSatelliteEnabledRequest != null) {
-                if (mSatelliteEnabledRequest.enableSatellite && !argument.enableSatellite
-                        && mWaitingForRadioDisabled) {
-                    // Previous mSatelliteEnabledRequest is successful but waiting for
-                    // all radios to be turned off.
-                    mSatelliteEnabledRequest.callback.accept(SATELLITE_RESULT_SUCCESS);
-                    resetSatelliteEnabledRequest();
-                } else if (mSatelliteEnabledRequest.requestId == argument.requestId) {
-                    resetSatelliteEnabledRequest();
-                }
-            }
-        }
         argument.callback.accept(SATELLITE_RESULT_MODEM_TIMEOUT);
-
         synchronized (mIsSatelliteEnabledLock) {
             if (argument.enableSatellite) {
-                if (!mWaitingForDisableSatelliteModemResponse && !mWaitingForSatelliteModemOff) {
-                    resetSatelliteEnabledRequest();
-                    IIntegerConsumer callback = new IIntegerConsumer.Stub() {
-                        @Override
-                        public void accept(int result) {
-                            plogd("handleEventWaitForSatelliteEnablingResponseTimedOut: "
-                                    + "disable satellite result=" + result);
-                        }
-                    };
-                    Consumer<Integer> result =
-                            FunctionalUtils.ignoreRemoteException(callback::accept);
-                    RequestSatelliteEnabledArgument request = new RequestSatelliteEnabledArgument(
-                            false, false, false, result);
-                    synchronized (mSatelliteEnabledRequestLock) {
-                        mSatelliteEnabledRequest = request;
+                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);
                     }
-                    sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, null);
                 }
-                notifyEnablementFailedToSatelliteSessionController();
+
                 mControllerMetricsStats.reportServiceEnablementFailCount();
                 mSessionMetricsStats.setInitializationResult(SATELLITE_RESULT_MODEM_TIMEOUT)
                         .setSatelliteTechnology(getSupportedNtnRadioTechnology())
                         .setInitializationProcessingTime(
                                 System.currentTimeMillis() - mSessionProcessingTimeStamp)
                         .setIsDemoMode(mIsDemoModeEnabled)
+                        .setCarrierId(getSatelliteCarrierId())
                         .reportSessionMetrics();
-                mSessionStartTimeStamp = 0;
-                mSessionProcessingTimeStamp = 0;
             } else {
-                /*
-                 * Unregister Importance Listener for Pointing UI when Satellite is disabled
-                 */
-                synchronized (mNeedsSatellitePointingLock) {
-                    if (mNeedsSatellitePointing) {
-                        mPointingAppController.removeListenerForPointingUI();
-                    }
-                }
-                moveSatelliteToOffStateAndCleanUpResources(SATELLITE_RESULT_MODEM_TIMEOUT, null);
+                resetSatelliteDisabledRequest();
                 mControllerMetricsStats.onSatelliteDisabled();
-                mWaitingForDisableSatelliteModemResponse = false;
-                mWaitingForSatelliteModemOff = false;
                 mSessionMetricsStats.setTerminationResult(SATELLITE_RESULT_MODEM_TIMEOUT)
                         .setSatelliteTechnology(getSupportedNtnRadioTechnology())
                         .setTerminationProcessingTime(
                                 System.currentTimeMillis() - mSessionProcessingTimeStamp)
                         .setSessionDurationSec(calculateSessionDurationTimeSec())
                         .reportSessionMetrics();
-                mSessionStartTimeStamp = 0;
-                mSessionProcessingTimeStamp = 0;
             }
+            notifyEnablementFailedToSatelliteSessionController(argument.enableSatellite);
+            mSessionStartTimeStamp = 0;
+            mSessionProcessingTimeStamp = 0;
         }
     }
 
@@ -4668,7 +5936,7 @@
             return;
         }
 
-        if (!isSatelliteEnabled()) {
+        if (!isSatelliteEnabledOrBeingEnabled()) {
             plogd("handleCmdUpdateNtnSignalStrengthReporting: ignore request, satellite is "
                     + "disabled");
             return;
@@ -4730,102 +5998,213 @@
         return true;
     }
 
-    private void determineSystemNotification() {
+    private void determineAutoConnectSystemNotification() {
         if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
             logd("determineSystemNotification: carrierEnabledSatelliteFlag is disabled");
             return;
         }
 
         Pair<Boolean, Integer> isNtn = isUsingNonTerrestrialNetworkViaCarrier();
+        boolean notificationKeyStatus = mSharedPreferences.getBoolean(
+                SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false);
+        if (DEBUG) {
+            logd("determineAutoConnectSystemNotification: isNtn.first = " + isNtn.first
+                    + " IsNotiToShow = " + !notificationKeyStatus + " mIsNotificationShowing = "
+                    + mIsNotificationShowing);
+        }
         if (isNtn.first) {
-            if (mSharedPreferences == null) {
-                try {
-                    mSharedPreferences = mContext.getSharedPreferences(SATELLITE_SHARED_PREF,
-                            Context.MODE_PRIVATE);
-                } catch (Exception e) {
-                    loge("Cannot get default shared preferences: " + e);
-                }
+            if (!notificationKeyStatus && getCarrierRoamingNtnConnectType(isNtn.second)
+                    == CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC) {
+                updateSatelliteSystemNotification(isNtn.second,
+                        CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC,
+                        /*visible*/ true);
             }
-            if (mSharedPreferences == null) {
-                loge("determineSystemNotification: Cannot get default shared preferences");
-                return;
-            }
-            if (!mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false)) {
-                showSatelliteSystemNotification(isNtn.second);
-                mSharedPreferences.edit().putBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY,
-                        true).apply();
-            }
+        } else if (mIsNotificationShowing
+                && !isSatelliteConnectedViaCarrierWithinHysteresisTime()) {
+            // Dismiss the notification if it is still displaying.
+            dismissSatelliteNotification();
         }
     }
 
-    private void showSatelliteSystemNotification(int subId) {
-        plogd("showSatelliteSystemNotification");
+    private void dismissSatelliteNotification() {
+        mIsNotificationShowing = false;
+        updateSatelliteSystemNotification(-1, -1,/*visible*/ false);
+    }
+
+    /**
+     * 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
+     * at a time to prevent confusing the user, so the same NOTIFICATION_CHANNEL and NOTIFICATION_ID
+     * are used.
+     *
+     * @param subId The subId that provides the satellite connection.
+     * @param carrierRoamingNtnConnectType {@link CarrierConfigManager
+     * .CARRIER_ROAMING_NTN_CONNECT_TYPE}
+     * @param visible {@code true} to show the notification, {@code false} to cancel it.
+     */
+    private void updateSatelliteSystemNotification(int subId,
+            @CARRIER_ROAMING_NTN_CONNECT_TYPE int carrierRoamingNtnConnectType, boolean visible) {
+        plogd("updateSatelliteSystemNotification subId=" + subId + ", carrierRoamingNtnConnectType="
+                + SatelliteServiceUtils.carrierRoamingNtnConnectTypeToString(
+                carrierRoamingNtnConnectType) + ", visible=" + visible);
         final NotificationChannel notificationChannel = new NotificationChannel(
                 NOTIFICATION_CHANNEL_ID,
                 NOTIFICATION_CHANNEL,
-                NotificationManager.IMPORTANCE_DEFAULT
-        );
+                NotificationManager.IMPORTANCE_DEFAULT);
         notificationChannel.setSound(null, null);
         NotificationManager notificationManager = mContext.getSystemService(
                 NotificationManager.class);
+        if (notificationManager == null) {
+            ploge("updateSatelliteSystemNotification: notificationManager is null");
+            return;
+        }
+        if (!visible) { // Cancel if any.
+            notificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_ID, UserHandle.ALL);
+            return;
+        }
         notificationManager.createNotificationChannel(notificationChannel);
 
-        Notification.Builder notificationBuilder = new Notification.Builder(mContext)
-                .setContentTitle(mContext.getResources().getString(
-                        R.string.satellite_notification_title))
-                .setContentText(mContext.getResources().getString(
-                        R.string.satellite_notification_summary))
+        // if carrierRoamingNtnConnectType is CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC
+        int title = R.string.satellite_notification_title;
+        int summary = R.string.satellite_notification_summary;
+        if (carrierRoamingNtnConnectType
+                == CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_MANUAL) {
+            title = R.string.satellite_notification_manual_title;
+            summary = R.string.satellite_notification_manual_summary;
+        }
+
+        Notification.Builder notificationBuilder = new Notification.Builder(mContext,
+                NOTIFICATION_CHANNEL_ID)
+                .setContentTitle(mContext.getResources().getString(title))
+                .setContentText(mContext.getResources().getString(summary))
                 .setSmallIcon(R.drawable.ic_android_satellite_24px)
-                .setChannelId(NOTIFICATION_CHANNEL_ID)
                 .setAutoCancel(true)
                 .setColor(mContext.getColor(
                         com.android.internal.R.color.system_notification_accent_color))
                 .setVisibility(Notification.VISIBILITY_PUBLIC);
 
-        // Add action to invoke message application.
-        // getDefaultSmsPackage and getLaunchIntentForPackage are nullable.
-        Optional<Intent> nullableIntent = Optional.ofNullable(
-                        Telephony.Sms.getDefaultSmsPackage(mContext))
-                .flatMap(packageName -> {
-                    PackageManager pm = mContext.getPackageManager();
-                    return Optional.ofNullable(pm.getLaunchIntentForPackage(packageName));
-                });
-        // If nullableIntent is null, create new Intent for most common way to invoke message app.
-        Intent finalIntent = nullableIntent.map(intent -> {
-            // Invoke the home screen of default message application.
-            intent.setAction(Intent.ACTION_MAIN);
-            intent.addCategory(Intent.CATEGORY_HOME);
-            return intent;
-        }).orElseGet(() -> {
-            ploge("showSatelliteSystemNotification: no default sms package name, Invoke "
-                    + "default sms compose window instead");
-            Intent newIntent = new Intent(Intent.ACTION_VIEW);
-            newIntent.setData(Uri.parse("sms:"));
-            return newIntent;
-        });
-
-        PendingIntent pendingIntentOpenMessage = PendingIntent.getActivity(mContext, 0,
-                finalIntent, PendingIntent.FLAG_IMMUTABLE);
+        // Intent for `Open Messages` [Button 1]
+        Intent openMessageIntent = new Intent();
+        openMessageIntent.setAction(OPEN_MESSAGE_BUTTON);
+        PendingIntent openMessagePendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                openMessageIntent, PendingIntent.FLAG_IMMUTABLE);
         Notification.Action actionOpenMessage = new Notification.Action.Builder(0,
                 mContext.getResources().getString(R.string.satellite_notification_open_message),
-                pendingIntentOpenMessage).build();
-        notificationBuilder.addAction(actionOpenMessage);
+                openMessagePendingIntent).build();
+        notificationBuilder.addAction(actionOpenMessage);   // Handle `Open Messages` button
 
-        // Add action to invoke Satellite setting activity in Settings.
-        Intent intentSatelliteSetting = new Intent(ACTION_SATELLITE_SETTING);
-        intentSatelliteSetting.putExtra("sub_id", subId);
-        PendingIntent pendingIntentSatelliteSetting = PendingIntent.getActivity(mContext, 0,
-                intentSatelliteSetting, PendingIntent.FLAG_IMMUTABLE);
-
-        Notification.Action actionOpenSatelliteSetting = new Notification.Action.Builder(null,
+        // Button for `How it works` [Button 2]
+        Intent howItWorksIntent = new Intent();
+        howItWorksIntent.setAction(HOW_IT_WORKS_BUTTON);
+        howItWorksIntent.putExtra("SUBID", subId);
+        PendingIntent howItWorksPendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                howItWorksIntent, PendingIntent.FLAG_IMMUTABLE);
+        Notification.Action actionHowItWorks = new Notification.Action.Builder(0,
                 mContext.getResources().getString(R.string.satellite_notification_how_it_works),
-                pendingIntentSatelliteSetting).build();
-        notificationBuilder.addAction(actionOpenSatelliteSetting);
+                howItWorksPendingIntent).build();
+        notificationBuilder.addAction(actionHowItWorks);    // Handle `How it works` button
+
+        // Intent for clicking the main notification body
+        Intent notificationClickIntent = new Intent(ACTION_NOTIFICATION_CLICK);
+        PendingIntent notificationClickPendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                notificationClickIntent, PendingIntent.FLAG_IMMUTABLE);
+        notificationBuilder.setContentIntent(
+                notificationClickPendingIntent); // Handle notification body click
+
+        // Intent for dismissing/swiping the notification
+        Intent deleteIntent = new Intent(ACTION_NOTIFICATION_DISMISS);
+        PendingIntent deletePendingIntent = PendingIntent.getBroadcast(mContext, 0, deleteIntent,
+                PendingIntent.FLAG_IMMUTABLE);
+        notificationBuilder.setDeleteIntent(
+                deletePendingIntent);  // Handle notification swipe/dismiss
 
         notificationManager.notifyAsUser(NOTIFICATION_TAG, NOTIFICATION_ID,
                 notificationBuilder.build(), UserHandle.ALL);
 
+        // The Intent filter is to receive the above four events.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(OPEN_MESSAGE_BUTTON);
+        filter.addAction(HOW_IT_WORKS_BUTTON);
+        filter.addAction(ACTION_NOTIFICATION_CLICK);
+        filter.addAction(ACTION_NOTIFICATION_DISMISS);
+        mContext.registerReceiver(mNotificationInteractionBroadcastReceiver, filter,
+                Context.RECEIVER_EXPORTED);
+
+        mIsNotificationShowing = true;
         mCarrierRoamingSatelliteControllerStats.reportCountOfSatelliteNotificationDisplayed();
+        mCarrierRoamingSatelliteControllerStats.reportCarrierId(getSatelliteCarrierId());
+        mSessionMetricsStats.addCountOfSatelliteNotificationDisplayed();
+    }
+
+    private final BroadcastReceiver mNotificationInteractionBroadcastReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent receivedIntent) {
+                    String intentAction = receivedIntent.getAction();
+                    if (TextUtils.isEmpty(intentAction)) {
+                        loge("Received empty action from the notification");
+                        return;
+                    }
+                    if (DBG) {
+                        plogd("Notification Broadcast recvd action = "
+                                + receivedIntent.getAction());
+                    }
+                    boolean closeStatusBar = true;
+                    switch (intentAction) {
+                        case OPEN_MESSAGE_BUTTON -> {
+                            // Add action to invoke message application.
+                            // getDefaultSmsPackage and getLaunchIntentForPackage are nullable.
+                            Optional<Intent> nullableIntent = Optional.ofNullable(
+                                    Telephony.Sms.getDefaultSmsPackage(context)).flatMap(
+                                    packageName -> {
+                                        PackageManager pm = context.getPackageManager();
+                                        return Optional.ofNullable(
+                                                pm.getLaunchIntentForPackage(packageName));
+                                    });
+                            // If nullableIntent is null, create new Intent for most common way to
+                            // invoke
+                            // message app.
+                            Intent finalIntent = nullableIntent.map(intent -> {
+                                // Invoke the home screen of default message application.
+                                intent.setAction(Intent.ACTION_MAIN);
+                                intent.addCategory(Intent.CATEGORY_HOME);
+                                return intent;
+                            }).orElseGet(() -> {
+                                ploge("showSatelliteSystemNotification: no default sms package "
+                                        + "name, Invoke default sms compose window instead");
+                                Intent newIntent = new Intent(Intent.ACTION_VIEW);
+                                newIntent.setData(Uri.parse("sms:"));
+                                return newIntent;
+                            });
+                            context.startActivity(finalIntent);
+                        }
+                        case HOW_IT_WORKS_BUTTON -> {
+                            int subId = receivedIntent.getIntExtra("SUBID", -1);
+                            Intent intentSatelliteSetting = new Intent(ACTION_SATELLITE_SETTING);
+                            intentSatelliteSetting.putExtra("sub_id", subId);
+                            intentSatelliteSetting.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                            context.startActivity(intentSatelliteSetting);
+
+                        }
+                        case ACTION_NOTIFICATION_DISMISS -> closeStatusBar = false;
+                    }
+                    // Note : ACTION_NOTIFICATION_DISMISS is not required to handled
+                    dismissNotificationAndUpdatePref(closeStatusBar);
+                }
+            };
+
+    private void dismissNotificationAndUpdatePref(boolean closeStatusBar) {
+        dismissSatelliteNotification();
+        if (closeStatusBar) {
+            // Collapse the status bar once user interact with notification.
+            StatusBarManager statusBarManager = mContext.getSystemService(StatusBarManager.class);
+            if (statusBarManager != null) {
+                statusBarManager.collapsePanels();
+            }
+        }
+        // update the sharedpref only when user interacted with the notification.
+        mSharedPreferences.edit().putBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, true).apply();
+        mContext.unregisterReceiver(mNotificationInteractionBroadcastReceiver);
     }
 
     private void resetCarrierRoamingSatelliteModeParams() {
@@ -4846,8 +6225,15 @@
         }
     }
 
+    /**
+     * Read carrier config items for satellite
+     *
+     * @param subId Associated subscription ID
+     * @return PersistableBundle including carrier config values
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     @NonNull
-    private PersistableBundle getPersistableBundle(int subId) {
+    public PersistableBundle getPersistableBundle(int subId) {
         synchronized (mCarrierConfigArrayLock) {
             PersistableBundle config = mCarrierConfigArray.get(subId);
             if (config == null) {
@@ -4866,15 +6252,61 @@
                 - mSessionMetricsStats.getSessionTerminationProcessingTimeMillis()) / 1000);
     }
 
-    private void notifyEnablementFailedToSatelliteSessionController() {
+    private void notifyEnablementFailedToSatelliteSessionController(boolean enabled) {
         if (mSatelliteSessionController != null) {
-            mSatelliteSessionController.onSatelliteEnablementFailed();
+            mSatelliteSessionController.onSatelliteEnablementFailed(enabled);
         } else {
             ploge("notifyEnablementFailedToSatelliteSessionController: mSatelliteSessionController"
                     + " is not initialized yet");
         }
     }
 
+    private void abortSatelliteEnableRequest(@SatelliteManager.SatelliteResult int resultCode) {
+        synchronized (mSatelliteEnabledRequestLock) {
+            if (mSatelliteEnabledRequest != null) {
+                plogw("abortSatelliteEnableRequest");
+                if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                    resultCode = SATELLITE_RESULT_REQUEST_ABORTED;
+                }
+                mSatelliteEnabledRequest.callback.accept(resultCode);
+                stopWaitForSatelliteEnablingResponseTimer(mSatelliteEnabledRequest);
+                mSatelliteEnabledRequest = null;
+            }
+        }
+    }
+
+    private void abortSatelliteDisableRequest(@SatelliteManager.SatelliteResult int resultCode) {
+        synchronized (mSatelliteEnabledRequestLock) {
+            if (mSatelliteDisabledRequest != null) {
+                plogd("abortSatelliteDisableRequest");
+                mSatelliteDisabledRequest.callback.accept(resultCode);
+                stopWaitForSatelliteEnablingResponseTimer(mSatelliteDisabledRequest);
+                mSatelliteDisabledRequest = null;
+            }
+        }
+    }
+
+    private void abortSatelliteEnableAttributesUpdateRequest(
+            @SatelliteManager.SatelliteResult int resultCode) {
+        synchronized (mSatelliteEnabledRequestLock) {
+            if (mSatelliteEnableAttributesUpdateRequest != null) {
+                plogd("abortSatelliteEnableAttributesUpdateRequest");
+                if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                    resultCode = SATELLITE_RESULT_REQUEST_ABORTED;
+                }
+                mSatelliteEnableAttributesUpdateRequest.callback.accept(resultCode);
+                stopWaitForUpdateSatelliteEnableAttributesResponseTimer(
+                        mSatelliteEnableAttributesUpdateRequest);
+                mSatelliteEnableAttributesUpdateRequest = null;
+            }
+        }
+    }
+
+    private void stopWaitForEnableResponseTimers() {
+        plogd("stopWaitForEnableResponseTimers");
+        removeMessages(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT);
+    }
+
     private long getDemoPointingAlignedDurationMillisFromResources() {
         long durationMillis = 15000L;
         try {
@@ -4909,10 +6341,13 @@
         return mDemoPointingNotAlignedDurationMillis;
     }
 
-    private boolean getWwanIsInService(ServiceState serviceState) {
+    /** Returns {@code true} if WWAN is in service, else {@code false}.*/
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean getWwanIsInService(@NonNull ServiceState serviceState) {
         List<NetworkRegistrationInfo> nriList = serviceState
                 .getNetworkRegistrationInfoListForTransportType(
                         AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
         for (NetworkRegistrationInfo nri : nriList) {
             if (nri.isInService()) {
                 logv("getWwanIsInService: return true");
@@ -4974,6 +6409,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;
@@ -5033,4 +6475,955 @@
         }
         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.
+     * 2. Carrier Enabled eSOS profile is higher priority than OEM enabled eSOS profile.
+     * 3. Among active carrier eSOS profiles user selected(default SMS SIM) eSOS profile will be
+     * the highest priority.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected void evaluateESOSProfilesPrioritization() {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("evaluateESOSProfilesPrioritization: Flag CarrierRoamingNbIotNtn is disabled");
+            return;
+        }
+        boolean isChanged = false;
+        List<SubscriptionInfo> allSubInfos = mSubscriptionManagerService.getAllSubInfoList(
+                mContext.getOpPackageName(), mContext.getAttributionTag());
+        // Key : priority - lower value has higher priority; Value : List<SubscriptionInfo>
+        TreeMap<Integer, List<SubscriptionInfo>> newSubsInfoListPerPriority = new TreeMap<>();
+        plogd("evaluateESOSProfilesPrioritization: allSubInfos.size()=" + allSubInfos.size());
+        synchronized (mSatelliteTokenProvisionedLock) {
+            for (SubscriptionInfo info : allSubInfos) {
+                int subId = info.getSubscriptionId();
+                boolean isActive = info.isActive();
+                boolean isDefaultSmsSubId =
+                        mSubscriptionManagerService.getDefaultSmsSubId() == subId;
+                boolean isNtnOnly = info.isOnlyNonTerrestrialNetwork();
+                boolean isESOSSupported = info.isSatelliteESOSSupported();
+                if (!isNtnOnly && !isESOSSupported) {
+                    continue;
+                }
+                if (!isActive && !isNtnOnly) {
+                    continue;
+                }
+
+                int keyPriority = (isESOSSupported && isActive && isDefaultSmsSubId) ? 0
+                        : (isESOSSupported && isActive) ? 1
+                                : (isNtnOnly) ? 2 : (isESOSSupported) ? 3 : -1;
+                if (keyPriority != -1) {
+                    newSubsInfoListPerPriority.computeIfAbsent(keyPriority,
+                            k -> new ArrayList<>()).add(info);
+                } else {
+                    plogw("evaluateESOSProfilesPrioritization: Got -1 keyPriority for subId="
+                            + info.getSubscriptionId());
+                }
+
+                Pair<String, Integer> subscriberIdPair = getSubscriberIdAndType(info);
+                String newSubscriberId = subscriberIdPair.first;
+                Optional<String> oldSubscriberId = mSubscriberIdPerSub.entrySet().stream()
+                        .filter(entry -> entry.getValue().equals(subId))
+                        .map(Map.Entry::getKey).findFirst();
+
+                if (oldSubscriberId.isPresent()
+                        && !newSubscriberId.equals(oldSubscriberId.get())) {
+                    mSubscriberIdPerSub.remove(oldSubscriberId.get());
+                    mProvisionedSubscriberId.remove(oldSubscriberId.get());
+                    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");
+            return;
+        }
+
+        // If priority has changed, send broadcast for provisioned ESOS subs IDs
+        synchronized (mSatelliteTokenProvisionedLock) {
+            if (isPriorityChanged(mSubsInfoListPerPriority, newSubsInfoListPerPriority)
+                    || isChanged) {
+                mSubsInfoListPerPriority = newSubsInfoListPerPriority;
+                sendBroadCastForProvisionedESOSSubs();
+                mHasSentBroadcast = true;
+                selectBindingSatelliteSubscription();
+            }
+        }
+    }
+
+    // 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(@Nullable SubscriptionInfo info) {
+        String subscriberId = "";
+        @SatelliteSubscriberInfo.SubscriberIdType int subscriberIdType =
+                SatelliteSubscriberInfo.ICCID;
+        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);
+        return new Pair<>(subscriberId, subscriberIdType);
+    }
+
+    private String getPhoneNumberBasedCarrier(int subId) {
+        SubscriptionInfoInternal internal = mSubscriptionManagerService.getSubscriptionInfoInternal(
+                subId);
+        SubscriptionManager subscriptionManager = mContext.getSystemService(
+                SubscriptionManager.class);
+        if (mInjectSubscriptionManager != null) {
+            logd("getPhoneNumberBasedCarrier: InjectSubscriptionManager");
+            subscriptionManager = mInjectSubscriptionManager;
+        }
+        String phoneNumber = subscriptionManager.getPhoneNumber(subId);
+        if (phoneNumber == null) {
+            logd("getPhoneNumberBasedCarrier: phoneNumber null");
+            return "";
+        }
+        return internal.getImsi() == null ? "" : internal.getImsi().substring(0, 6)
+                + phoneNumber.replaceFirst("^\\+", "");
+    }
+
+    private boolean isPriorityChanged(Map<Integer, List<SubscriptionInfo>> currentMap,
+            Map<Integer, List<SubscriptionInfo>> newMap) {
+        if (currentMap.size() == 0 || currentMap.size() != newMap.size()) {
+            return true;
+        }
+
+        for (Map.Entry<Integer, List<SubscriptionInfo>> entry : currentMap.entrySet()) {
+            List<SubscriptionInfo> currentList = entry.getValue();
+            List<SubscriptionInfo> newList = newMap.get(entry.getKey());
+            if (newList == null || currentList == null || currentList.size() != newList.size()) {
+                return true;
+            }
+            for (int i = 0; i < currentList.size(); i++) {
+                if (currentList.get(i).getSubscriptionId() != newList.get(i).getSubscriptionId()) {
+                    logd("isPriorityChanged: cur=" + currentList.get(i) + " , new=" + newList.get(
+                            i));
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void sendBroadCastForProvisionedESOSSubs() {
+        String packageName = getConfigSatelliteGatewayServicePackage();
+        String className = getConfigSatelliteCarrierRoamingEsosProvisionedClass();
+        if (packageName == null || className == null || packageName.isEmpty()
+                || className.isEmpty()) {
+            logd("sendBroadCastForProvisionedESOSSubs: packageName or className is null or empty.");
+            return;
+        }
+        String action = SatelliteManager.ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED;
+
+        Intent intent = new Intent(action);
+        intent.setComponent(new ComponentName(packageName, className));
+        if (mFeatureFlags.hsumBroadcast()) {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        } else {
+            mContext.sendBroadcast(intent);
+        }
+        logd("sendBroadCastForProvisionedESOSSubs" + intent);
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected String getStringFromOverlayConfig(int resourceId) {
+        String name;
+        try {
+            name = mContext.getResources().getString(resourceId);
+        } catch (Resources.NotFoundException ex) {
+            loge("getStringFromOverlayConfig: ex=" + ex);
+            name = null;
+        }
+        return name;
+    }
+
+    /**
+     * 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
+     * to be used for provision if the request is successful or an error code if the request failed.
+     */
+    public void requestSatelliteSubscriberProvisionStatus(@NonNull ResultReceiver result) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            result.send(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, null);
+            return;
+        }
+        List<SatelliteSubscriberProvisionStatus> list =
+                getPrioritizedSatelliteSubscriberProvisionStatusList();
+        logd("requestSatelliteSubscriberProvisionStatus: " + list);
+        final Bundle bundle = new Bundle();
+        bundle.putParcelableList(SatelliteManager.KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN, list);
+        result.send(SATELLITE_RESULT_SUCCESS, bundle);
+    }
+
+    private List<SatelliteSubscriberProvisionStatus>
+            getPrioritizedSatelliteSubscriberProvisionStatusList() {
+        List<SatelliteSubscriberProvisionStatus> list = new ArrayList<>();
+        synchronized (mSatelliteTokenProvisionedLock) {
+            for (int priority : mSubsInfoListPerPriority.keySet()) {
+                List<SubscriptionInfo> infoList = mSubsInfoListPerPriority.get(priority);
+                if (infoList == null) {
+                    logd("getPrioritySatelliteSubscriberProvisionStatusList: no exist this "
+                            + "priority " + priority);
+                    continue;
+                }
+                for (SubscriptionInfo info : infoList) {
+                    Pair<String, Integer> subscriberIdPair = getSubscriberIdAndType(info);
+                    String subscriberId = subscriberIdPair.first;
+                    int carrierId = info.getCarrierId();
+                    String apn = getConfigForSubId(info.getSubscriptionId())
+                            .getString(KEY_SATELLITE_NIDD_APN_NAME_STRING, "");
+                    logd("getPrioritySatelliteSubscriberProvisionStatusList: subscriberId:"
+                            + subscriberId + " , carrierId=" + carrierId + " , apn=" + apn);
+                    if (subscriberId.isEmpty()) {
+                        logd("getPrioritySatelliteSubscriberProvisionStatusList: getSubscriberId "
+                                + "failed skip this subscriberId.");
+                        continue;
+                    }
+                    SatelliteSubscriberInfo satelliteSubscriberInfo =
+                            new SatelliteSubscriberInfo.Builder().setSubscriberId(subscriberId)
+                                    .setCarrierId(carrierId).setNiddApn(apn)
+                                    .setSubId(info.getSubscriptionId())
+                                    .setSubscriberIdType(subscriberIdPair.second)
+                                    .build();
+                    boolean provisioned = mProvisionedSubscriberId.getOrDefault(subscriberId,
+                            false);
+                    logd("getPrioritySatelliteSubscriberProvisionStatusList: "
+                            + "satelliteSubscriberInfo=" + satelliteSubscriberInfo
+                            + ", provisioned=" + provisioned);
+                    list.add(new SatelliteSubscriberProvisionStatus.Builder()
+                            .setSatelliteSubscriberInfo(satelliteSubscriberInfo)
+                            .setProvisionStatus(provisioned).build());
+                    mSubscriberIdPerSub.put(subscriberId, info.getSubscriptionId());
+                }
+            }
+        }
+        return list;
+    }
+
+    public int getSelectedSatelliteSubId() {
+        synchronized (mSatelliteTokenProvisionedLock) {
+            return mSelectedSatelliteSubId;
+        }
+    }
+
+    private void selectBindingSatelliteSubscription() {
+        if (isSatelliteEnabled() || isSatelliteBeingEnabled()) {
+            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) {
+            // TODO: need to check if satellite is allowed at current location for the subscription
+            int subId = getSubIdFromSubscriberId(
+                    status.getSatelliteSubscriberInfo().getSubscriberId());
+            if (status.getProvisionStatus() && isActiveSubId(subId)) {
+                selectedSubId = subId;
+                break;
+            }
+        }
+
+        synchronized (mSatelliteTokenProvisionedLock) {
+            if (selectedSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                    && isSatelliteSupportedViaOem()) {
+                selectedSubId = getNtnOnlySubscriptionId();
+            }
+            mSelectedSatelliteSubId = selectedSubId;
+            setSatellitePhone(selectedSubId);
+        }
+        plogd("selectBindingSatelliteSubscription: SelectedSatelliteSubId="
+                + mSelectedSatelliteSubId);
+    }
+
+    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);
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("isSubscriptionProvisioned: carrierRoamingNbIotNtn flag is disabled");
+            return false;
+        }
+
+        String subscriberId = getSubscriberIdAndType(
+                mSubscriptionManagerService.getSubscriptionInfo(subId)).first;
+        if (subscriberId.isEmpty()) {
+            plogd("isSubscriptionProvisioned: subId=" + subId + " subscriberId is empty.");
+            return false;
+        }
+
+        synchronized (mSatelliteTokenProvisionedLock) {
+            return mProvisionedSubscriberId.getOrDefault(subscriberId, false);
+        }
+    }
+
+    /**
+     * Deliver the list of provisioned satellite subscriber ids.
+     *
+     * @param list List of provisioned satellite subscriber ids.
+     * @param result The result receiver that returns whether deliver success or fail.
+     */
+    public void provisionSatellite(@NonNull List<SatelliteSubscriberInfo> list,
+            @NonNull ResultReceiver result) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            result.send(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, null);
+            logd("provisionSatellite: carrierRoamingNbIotNtn not support");
+            return;
+        }
+        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, true);
+        sendRequestAsync(CMD_UPDATE_PROVISION_SATELLITE_TOKEN, request, null);
+    }
+
+    /**
+     * 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);
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void setSatellitePhone(int subId) {
+        synchronized (mSatellitePhoneLock) {
+            mSatellitePhone = SatelliteServiceUtils.getPhone(subId);
+            if (mSatellitePhone == null) {
+                mSatellitePhone = SatelliteServiceUtils.getPhone();
+            }
+            plogd("mSatellitePhone: phoneId=" + (mSatellitePhone != null
+                      ? mSatellitePhone.getPhoneId() : "null") + ", subId=" + subId);
+            int carrierId = mSatellitePhone.getCarrierId();
+            if (carrierId != UNKNOWN_CARRIER_ID) {
+                mControllerMetricsStats.setCarrierId(carrierId);
+            } else {
+                logd("setSatellitePhone: Carrier ID is UNKNOWN_CARRIER_ID");
+            }
+        }
+    }
+
+    /** Return the carrier ID of the binding satellite subscription. */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public int getSatelliteCarrierId() {
+        synchronized (mSatelliteTokenProvisionedLock) {
+            SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(
+                    mSelectedSatelliteSubId);
+            if (subInfo == null) {
+                logd("getSatelliteCarrierId: returns UNKNOWN_CARRIER_ID");
+                return UNKNOWN_CARRIER_ID;
+            }
+            return subInfo.getCarrierId();
+        }
+    }
+
+    /**
+     * Get whether phone is eligible to connect to carrier roaming non-terrestrial network.
+     *
+     * @param phone phone object
+     * return {@code true} when the subscription is eligible for satellite
+     * communication if all the following conditions are met:
+     * <ul>
+     * <li>Subscription supports P2P satellite messaging which is defined by
+     * {@link CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} </li>
+     * <li>{@link CarrierConfigManager#KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT} set to
+     * {@link CarrierConfigManager#CARRIER_ROAMING_NTN_CONNECT_MANUAL} </li>
+     * <li>The device is in {@link ServiceState#STATE_OUT_OF_SERVICE}, not connected to Wi-Fi. </li>
+     * </ul>
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean isCarrierRoamingNtnEligible(@Nullable Phone phone) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("isCarrierRoamingNtnEligible: carrierRoamingNbIotNtn flag is disabled");
+            return false;
+        }
+
+        if (phone == null) {
+            plogd("isCarrierRoamingNtnEligible: phone is null");
+            return false;
+        }
+
+        int subId = phone.getSubId();
+        if (!isSatelliteRoamingP2pSmSSupported(subId)) {
+            plogd("isCarrierRoamingNtnEligible: doesn't support P2P SMS");
+            return false;
+        }
+
+        if (!isSatelliteSupportedViaCarrier(subId)) {
+            plogd("isCarrierRoamingNtnEligible[phoneId=" + phone.getPhoneId()
+                    + "]: satellite is not supported via carrier");
+            return false;
+        }
+
+        if (!isSubscriptionProvisioned(subId)) {
+            plogd("isCarrierRoamingNtnEligible[phoneId=" + phone.getPhoneId()
+                    + "]: subscription is not provisioned to use satellite.");
+            return false;
+        }
+
+        if (!isSatelliteServiceSupportedByCarrier(subId,
+                NetworkRegistrationInfo.SERVICE_TYPE_SMS)) {
+            plogd("isCarrierRoamingNtnEligible[phoneId=" + phone.getPhoneId()
+                    + "]: SMS is not supported by carrier");
+            return false;
+        }
+
+        int carrierRoamingNtnConnectType = getCarrierRoamingNtnConnectType(subId);
+        if (carrierRoamingNtnConnectType != CARRIER_ROAMING_NTN_CONNECT_MANUAL) {
+            plogd("isCarrierRoamingNtnEligible[phoneId=" + phone.getPhoneId() + "]: not manual "
+                    + "connect. carrierRoamingNtnConnectType = " + carrierRoamingNtnConnectType);
+            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");
+            return false;
+        }
+
+        synchronized (mIsWifiConnectedLock) {
+            if (mIsWifiConnected) {
+                plogd("isCarrierRoamingNtnEligible[phoneId=" + phone.getPhoneId()
+                        + "]: Wi-Fi is connected");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private boolean isSatelliteServiceSupportedByCarrier(int subId,
+            @NetworkRegistrationInfo.ServiceType int serviceType) {
+        List<String> satellitePlmnList = getSatellitePlmnsForCarrier(subId);
+        for (String satellitePlmn : satellitePlmnList) {
+            if (getSupportedSatelliteServicesForPlmn(subId, satellitePlmn).contains(serviceType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** return satellite phone */
+    @Nullable
+    public Phone getSatellitePhone() {
+        synchronized (mSatellitePhoneLock) {
+            return mSatellitePhone;
+        }
+    }
+
+    /** Start PointingUI if it is required. */
+    public void startPointingUI() {
+        synchronized (mNeedsSatellitePointingLock) {
+            plogd("startPointingUI: mNeedsSatellitePointing=" + mNeedsSatellitePointing
+                    + ", mIsDemoModeEnabled=" + mIsDemoModeEnabled
+                    + ", mIsEmergency=" + mIsEmergency);
+            if (mNeedsSatellitePointing) {
+                mPointingAppController.startPointingUI(false /*needFullScreenPointingUI*/,
+                        mIsDemoModeEnabled, mIsEmergency);
+            }
+        }
+    }
+
+    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");
+            return;
+        }
+
+        satelliteManager.requestIsCommunicationAllowedForCurrentLocation(
+                this::post, callback);
+    }
+
+    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 handleEventSatelliteRegistrationFailure(int causeCode) {
+        plogd("handleEventSatelliteRegistrationFailure: " + causeCode);
+
+        List<ISatelliteModemStateCallback> deadCallersList = new ArrayList<>();
+        mSatelliteRegistrationFailureListeners.values().forEach(listener -> {
+            try {
+                listener.onRegistrationFailure(causeCode);
+            } catch (RemoteException e) {
+                logd("handleEventSatelliteRegistrationFailure RemoteException: " + e);
+                deadCallersList.add(listener);
+            }
+        });
+        deadCallersList.forEach(listener -> {
+            mSatelliteRegistrationFailureListeners.remove(listener.asBinder());
+        });
+    }
+
+    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 :
+     * config_satellite_gateway_service_package and
+     * config_satellite_carrier_roaming_esos_provisioned_class.
+     * These values are set before sending an intent to broadcast there are any change to list of
+     * subscriber informations.
+     *
+     * @param name the name is one of the following that constitute an intent.
+     *             component package name, or component class name.
+     * @return {@code true} if the setting is successful, {@code false} otherwise.
+     */
+    public boolean setSatelliteSubscriberIdListChangedIntentComponent(String name) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            logd("setSatelliteSubscriberIdListChangedIntentComponent: carrierRoamingNbIotNtn is "
+                    + "disabled");
+            return false;
+        }
+        if (!isMockModemAllowed()) {
+            logd("setSatelliteSubscriberIdListChangedIntentComponent: mock modem is not allowed");
+            return false;
+        }
+        logd("setSatelliteSubscriberIdListChangedIntentComponent:" + name);
+
+        if (name.contains("/")) {
+            mChangeIntentComponent = true;
+        } else {
+            mChangeIntentComponent = false;
+            return true;
+        }
+        boolean result = true;
+        String[] cmdPart = name.split("/");
+        switch (cmdPart[0]) {
+            case "-p": {
+                mConfigSatelliteGatewayServicePackage = cmdPart[1];
+                break;
+            }
+            case "-c": {
+                mConfigSatelliteCarrierRoamingEsosProvisionedClass = cmdPart[1];
+                break;
+            }
+            default:
+                logd("setSatelliteSubscriberIdListChangedIntentComponent: invalid name " + name);
+                result = false;
+                break;
+        }
+        return result;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected String getConfigSatelliteGatewayServicePackage() {
+        if (!mChangeIntentComponent) {
+            return getStringFromOverlayConfig(
+                    R.string.config_satellite_gateway_service_package);
+        }
+        logd("getConfigSatelliteGatewayServicePackage: " + mConfigSatelliteGatewayServicePackage);
+        return mConfigSatelliteGatewayServicePackage;
+    }
+
+    private String getConfigSatelliteCarrierRoamingEsosProvisionedClass() {
+        if (!mChangeIntentComponent) {
+            return getStringFromOverlayConfig(
+                    R.string.config_satellite_carrier_roaming_esos_provisioned_class);
+        }
+        logd("getConfigSatelliteCarrierRoamingEsosProvisionedClass: "
+                + mConfigSatelliteCarrierRoamingEsosProvisionedClass);
+        return mConfigSatelliteCarrierRoamingEsosProvisionedClass;
+    }
+
+    private void registerDefaultSmsSubscriptionChangedBroadcastReceiver() {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("registerDefaultSmsSubscriptionChangedBroadcastReceiver: Flag "
+                    + "CarrierRoamingNbIotNtn is disabled");
+            return;
+        }
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED);
+        mContext.registerReceiver(mDefaultSmsSubscriptionChangedBroadcastReceiver, intentFilter);
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected List<DeviceState> getSupportedDeviceStates() {
+        return mContext.getSystemService(DeviceStateManager.class).getSupportedDeviceStates();
+    }
+
+    FeatureFlags getFeatureFlags() {
+        return mFeatureFlags;
+    }
+
+    private boolean isSatelliteDisabled() {
+        synchronized (mIsSatelliteEnabledLock) {
+            return ((mIsSatelliteEnabled != null) && !mIsSatelliteEnabled);
+        }
+    }
+
+    private boolean shouldStopWaitForEnableResponseTimer(
+            @NonNull RequestSatelliteEnabledArgument argument) {
+        if (argument.enableSatellite) return true;
+        synchronized (mSatelliteEnabledRequestLock) {
+            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() {
+        synchronized (this.mSatellitePhoneLock) {
+            if (this.mSatellitePhone == null) {
+                return;
+            }
+        }
+        int subId = getSelectedSatelliteSubId();
+        PersistableBundle config = getPersistableBundle(subId);
+        if (config.containsKey(KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE)) {
+            int datagramSize = config.getInt(KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE);
+            SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId);
+            if (!(subInfo == null || subInfo.isOnlyNonTerrestrialNetwork())) {
+                this.mSatelliteCapabilities.setMaxBytesPerOutgoingDatagram(datagramSize);
+            }
+        }
+    }
+
+    private 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();
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void handleCarrierRoamingNtnAvailableServicesChanged(int subId) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("handleCarrierRoamingNtnAvailableServicesChanged: "
+                    + "carrierRoamingNbIotNtn flag is disabled");
+            return;
+        }
+        updateLastNotifiedNtnAvailableServicesAndNotify(subId);
+    }
+
+    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());
+
+        if (isSatelliteSupportedViaCarrier(subId)) {
+            // TODO: Invoke SatelliteManager#getSatelliteDisallowedReasons() NOT EMPTY.
+            int[] services = getSupportedSatelliteServicesForCarrier(subId);
+            if (isP2PSmsDisallowedOnCarrierRoamingNtn(subId)) {
+                services = Arrays.stream(services).filter(
+                        value -> value != NetworkRegistrationInfo.SERVICE_TYPE_SMS).toArray();
+            }
+            phone.notifyCarrierRoamingNtnAvailableServicesChanged(services);
+        } else {
+            phone.notifyCarrierRoamingNtnAvailableServicesChanged(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 (!isApplicationSupportsP2P(mDefaultSmsPackageName)
+                    || !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);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
index da4c69b..5fa85db 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
@@ -40,6 +40,7 @@
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.SatelliteManager.SatelliteException;
+import android.telephony.satellite.SatelliteModemEnableRequestAttributes;
 import android.telephony.satellite.stub.INtnSignalStrengthConsumer;
 import android.telephony.satellite.stub.ISatellite;
 import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
@@ -86,8 +87,6 @@
     private boolean mIsBinding;
     @Nullable private PersistentLogger mPersistentLogger = null;
 
-    @NonNull private final RegistrantList mSatelliteProvisionStateChangedRegistrants =
-            new RegistrantList();
     @NonNull private final RegistrantList mSatellitePositionInfoChangedRegistrants =
             new RegistrantList();
     @NonNull private final RegistrantList mDatagramTransferStateChangedRegistrants =
@@ -103,6 +102,10 @@
             new RegistrantList();
     @NonNull private final RegistrantList mSatelliteSupportedStateChangedRegistrants =
             new RegistrantList();
+    @NonNull private final RegistrantList mSatelliteRegistrationFailureRegistrants =
+            new RegistrantList();
+    @NonNull private final RegistrantList mTerrestrialNetworkAvailableChangedRegistrants =
+            new RegistrantList();
 
     private class SatelliteListener extends ISatelliteListener.Stub {
 
@@ -113,11 +116,6 @@
         }
 
         @Override
-        public void onSatelliteProvisionStateChanged(boolean provisioned) {
-            mSatelliteProvisionStateChangedRegistrants.notifyResult(provisioned);
-        }
-
-        @Override
         public void onSatelliteDatagramReceived(
                 android.telephony.satellite.stub.SatelliteDatagram datagram, int pendingCount) {
             if (notifyResultIfExpectedListener()) {
@@ -193,7 +191,12 @@
 
         @Override
         public void onRegistrationFailure(int causeCode) {
-            // TO-DO notify registrants
+            mSatelliteRegistrationFailureRegistrants.notifyResult(causeCode);
+        }
+
+        @Override
+        public void onTerrestrialNetworkAvailableChanged(boolean isAvailable) {
+            mTerrestrialNetworkAvailableChangedRegistrants.notifyResult(isAvailable);
         }
 
         private boolean notifyResultIfExpectedListener() {
@@ -403,27 +406,6 @@
     }
 
     /**
-     * Registers for the satellite provision state changed.
-     *
-     * @param h Handler for notification message.
-     * @param what User-defined message code.
-     * @param obj User object.
-     */
-    public void registerForSatelliteProvisionStateChanged(
-            @NonNull Handler h, int what, @Nullable Object obj) {
-        mSatelliteProvisionStateChangedRegistrants.add(h, what, obj);
-    }
-
-    /**
-     * Unregisters for the satellite provision state changed.
-     *
-     * @param h Handler to be removed from the registrant list.
-     */
-    public void unregisterForSatelliteProvisionStateChanged(@NonNull Handler h) {
-        mSatelliteProvisionStateChangedRegistrants.remove(h);
-    }
-
-    /**
      * Registers for satellite position info changed from satellite modem.
      *
      * @param h Handler for notification message.
@@ -591,6 +573,48 @@
     }
 
     /**
+     * Registers for the satellite registration failed.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSatelliteRegistrationFailure(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+        mSatelliteRegistrationFailureRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregisters for the satellite registration failed.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSatelliteRegistrationFailure(@NonNull Handler h) {
+        mSatelliteRegistrationFailureRegistrants.remove(h);
+    }
+
+    /**
+     * 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.
      *
@@ -656,14 +680,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);
@@ -682,17 +706,16 @@
      * is enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
      * this may also re-enable the cellular modem.
      *
-     * @param enableSatellite True to enable the satellite modem and false to disable.
-     * @param enableDemoMode True to enable demo mode and false to disable.
-     * @param isEmergency {@code true} to enable emergency mode, {@code false} otherwise.
+     * @param enableAttributes info needed to allow carrier to roam to satellite.
      * @param message The Message to send to result of the operation to.
      */
-    public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
-            boolean isEmergency, @NonNull Message message) {
+    public void requestSatelliteEnabled(SatelliteModemEnableRequestAttributes enableAttributes,
+            @NonNull Message message) {
         if (mSatelliteService != null) {
             try {
-                mSatelliteService.requestSatelliteEnabled(enableSatellite, enableDemoMode,
-                        isEmergency, new IIntegerConsumer.Stub() {
+                mSatelliteService.requestSatelliteEnabled(SatelliteServiceUtils
+                        .toSatelliteModemEnableRequestAttributes(enableAttributes),
+                        new IIntegerConsumer.Stub() {
                             @Override
                             public void accept(int result) {
                                 int error = SatelliteServiceUtils.fromSatelliteError(result);
@@ -889,113 +912,6 @@
     }
 
     /**
-     * Provision the device with a satellite provider.
-     * This is needed if the provider allows dynamic registration.
-     * Once provisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report true.
-     *
-     * @param token The token to be used as a unique identifier for provisioning with satellite
-     *              gateway.
-     * @param provisionData Data from the provisioning app that can be used by provisioning server
-     * @param message The Message to send to result of the operation to.
-     */
-    public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
-            @NonNull Message message) {
-        if (mSatelliteService != null) {
-            try {
-                mSatelliteService.provisionSatelliteService(token, provisionData,
-                        new IIntegerConsumer.Stub() {
-                            @Override
-                            public void accept(int result) {
-                                int error = SatelliteServiceUtils.fromSatelliteError(result);
-                                plogd("provisionSatelliteService: " + error);
-                                Binder.withCleanCallingIdentity(() ->
-                                        sendMessageWithResult(message, null, error));
-                            }
-                        });
-            } catch (RemoteException e) {
-                ploge("provisionSatelliteService: RemoteException " + e);
-                sendMessageWithResult(message, null,
-                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
-            }
-        } else {
-            ploge("provisionSatelliteService: Satellite service is unavailable.");
-            sendMessageWithResult(message, null,
-                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
-        }
-    }
-
-    /**
-     * Deprovision the device with the satellite provider.
-     * This is needed if the provider allows dynamic registration.
-     * Once deprovisioned, ISatelliteListener#onSatelliteProvisionStateChanged should report false.
-     *
-     * @param token The token of the device/subscription to be deprovisioned.
-     * @param message The Message to send to result of the operation to.
-     */
-    public void deprovisionSatelliteService(@NonNull String token, @NonNull Message message) {
-        if (mSatelliteService != null) {
-            try {
-                mSatelliteService.deprovisionSatelliteService(token, new IIntegerConsumer.Stub() {
-                    @Override
-                    public void accept(int result) {
-                        int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        plogd("deprovisionSatelliteService: " + error);
-                        Binder.withCleanCallingIdentity(() ->
-                                sendMessageWithResult(message, null, error));
-                    }
-                });
-            } catch (RemoteException e) {
-                ploge("deprovisionSatelliteService: RemoteException " + e);
-                sendMessageWithResult(message, null,
-                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
-            }
-        } else {
-            ploge("deprovisionSatelliteService: Satellite service is unavailable.");
-            sendMessageWithResult(message, null,
-                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
-        }
-    }
-
-    /**
-     * Request to get whether this device is provisioned with a satellite provider.
-     *
-     * @param message The Message to send to result of the operation to.
-     */
-    public void requestIsSatelliteProvisioned(@NonNull Message message) {
-        if (mSatelliteService != null) {
-            try {
-                mSatelliteService.requestIsSatelliteProvisioned(new IIntegerConsumer.Stub() {
-                    @Override
-                    public void accept(int result) {
-                        int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        plogd("requestIsSatelliteProvisioned: " + error);
-                        Binder.withCleanCallingIdentity(() ->
-                                sendMessageWithResult(message, null, error));
-                    }
-                }, new IBooleanConsumer.Stub() {
-                    @Override
-                    public void accept(boolean result) {
-                        // Convert for compatibility with SatelliteResponse
-                        // TODO: This should just report result instead.
-                        int[] provisioned = new int[] {result ? 1 : 0};
-                        plogd("requestIsSatelliteProvisioned: " + Arrays.toString(provisioned));
-                        Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
-                                message, provisioned, SatelliteManager.SATELLITE_RESULT_SUCCESS));
-                    }
-                });
-            } catch (RemoteException e) {
-                ploge("requestIsSatelliteProvisioned: RemoteException " + e);
-                sendMessageWithResult(message, null,
-                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
-            }
-        } else {
-            ploge("requestIsSatelliteProvisioned: Satellite service is unavailable.");
-            sendMessageWithResult(message, null,
-                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
-        }
-    }
-
-    /**
      * Poll the pending datagrams to be received over satellite.
      * The satellite service should check if there are any pending datagrams to be received over
      * satellite and report them via ISatelliteListener#onSatelliteDatagramsReceived.
@@ -1410,6 +1326,34 @@
     }
 
     /**
+     * Provision UUID with a satellite provider.
+     */
+    public void updateSatelliteSubscription(@NonNull String iccId, @NonNull Message message) {
+        if (mSatelliteService != null) {
+            try {
+                mSatelliteService.updateSatelliteSubscription(iccId,
+                        new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                int error = SatelliteServiceUtils.fromSatelliteError(result);
+                                plogd("updateSatelliteSubscription: " + error);
+                                Binder.withCleanCallingIdentity(() ->
+                                        sendMessageWithResult(message, null, error));
+                            }
+                        });
+            } catch (RemoteException e) {
+                ploge("updateSatelliteSubscription: RemoteException " + e);
+                sendMessageWithResult(message, null,
+                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
+            }
+        } else {
+            ploge("updateSatelliteSubscription: Satellite service is unavailable.");
+            sendMessageWithResult(message, null,
+                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
+        }
+    }
+
+    /**
      * 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.
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
index 0e938c1..182f667 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
@@ -45,19 +45,17 @@
 import android.os.SystemProperties;
 import android.provider.DeviceConfig;
 import android.telecom.Connection;
-import android.telephony.AccessNetworkConstants;
 import android.telephony.DropBoxManagerLoggerBackend;
-import android.telephony.NetworkRegistrationInfo;
 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;
 import android.telephony.ims.RegistrationManager;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatelliteSubscriberProvisionStatus;
 import android.text.TextUtils;
 import android.util.Pair;
 import android.util.SparseArray;
@@ -70,6 +68,9 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.SmsApplication;
+import com.android.internal.telephony.TelephonyCountryDetector;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.metrics.SatelliteStats;
 
 import java.util.List;
@@ -97,7 +98,11 @@
     @NonNull private final Context mContext;
     @NonNull
     private final SatelliteController mSatelliteController;
+    @NonNull
+    private final TelephonyCountryDetector mCountryDetector;
     private ImsManager mImsManager;
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
 
     private Connection mEmergencyConnection = null;
     private final ISatelliteProvisionStateCallback mISatelliteProvisionStateCallback;
@@ -119,6 +124,8 @@
 
     @Nullable private PersistentLogger mPersistentLogger = null;
 
+    private boolean mIsTestEmergencyNumber = false;
+
     /**
      * Create an instance of SatelliteSOSMessageRecommender.
      *
@@ -151,6 +158,8 @@
         }
         mContext = context;
         mSatelliteController = satelliteController;
+        mFeatureFlags = mSatelliteController.getFeatureFlags();
+        mCountryDetector = TelephonyCountryDetector.getInstance(context, mFeatureFlags);
         mImsManager = imsManager;
         mOemEnabledTimeoutMillis =
                 getOemEnabledEmergencyCallWaitForConnectionTimeoutMillis(context);
@@ -160,6 +169,13 @@
                 plogd("onSatelliteProvisionStateChanged: provisioned=" + provisioned);
                 sendMessage(obtainMessage(EVENT_SATELLITE_PROVISIONED_STATE_CHANGED, provisioned));
             }
+
+            @Override
+            public void onSatelliteSubscriptionProvisionStateChanged(
+                    List<SatelliteSubscriberProvisionStatus> satelliteSubscriberProvisionStatus) {
+                plogd("onSatelliteSubscriptionProvisionStateChanged: "
+                        + satelliteSubscriberProvisionStatus);
+            }
         };
     }
 
@@ -199,11 +215,13 @@
      * @param connection The connection created by TelephonyConnectionService for the emergency
      *                   call.
      */
-    public void onEmergencyCallStarted(@NonNull Connection connection) {
+    public void onEmergencyCallStarted(@NonNull Connection connection,
+            boolean isTestEmergencyNumber) {
         if (!isSatelliteSupported()) {
             plogd("onEmergencyCallStarted: satellite is not supported");
             return;
         }
+        mIsTestEmergencyNumber = isTestEmergencyNumber;
 
         if (hasMessages(EVENT_EMERGENCY_CALL_STARTED)) {
             logd("onEmergencyCallStarted: Ignoring due to ongoing event:");
@@ -245,6 +263,7 @@
     }
 
     private void handleEmergencyCallStartedEvent(@NonNull Connection connection) {
+        plogd("handleEmergencyCallStartedEvent: connection=" + connection);
         mSatelliteController.setLastEmergencyCallTime();
 
         if (sendEventDisplayEmergencyMessageForcefully(connection)) {
@@ -266,7 +285,7 @@
 
     private void handleSatelliteProvisionStateChangedEvent(boolean provisioned) {
         if (!provisioned) {
-            cleanUpResources();
+            cleanUpResources(false);
         }
     }
 
@@ -299,29 +318,31 @@
             updateSatelliteViaCarrierAvailability();
 
             boolean isDialerNotified = false;
-            if (!isCellularAvailable()
+            boolean isCellularAvailable = SatelliteServiceUtils.isCellularAvailable();
+            if (!isCellularAvailable
                     && isSatelliteAllowed()
-                    && (isSatelliteViaOemAvailable() || isSatelliteViaCarrierAvailable())
+                    && (isDeviceProvisioned()
+                    || isSatelliteConnectedViaCarrierWithinHysteresisTime())
                     && shouldTrackCall(mEmergencyConnection.getState())) {
                 plogd("handleTimeoutEvent: Sent EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer");
-                Bundle extras = createExtraBundleForEventDisplayEmergencyMessage();
+                Bundle extras = createExtraBundleForEventDisplayEmergencyMessage(
+                        mIsTestEmergencyNumber);
                 mEmergencyConnection.sendConnectionEvent(
                         TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE, extras);
                 isDialerNotified = true;
 
             }
             plogd("handleTimeoutEvent: isImsRegistered=" + isImsRegistered()
-                    + ", isCellularAvailable=" + isCellularAvailable()
+                    + ", isCellularAvailable=" + isCellularAvailable
                     + ", isSatelliteAllowed=" + isSatelliteAllowed()
                     + ", shouldTrackCall=" + shouldTrackCall(mEmergencyConnection.getState()));
-            reportEsosRecommenderDecision(isDialerNotified);
-            cleanUpResources();
+            cleanUpResources(isDialerNotified);
         }
     }
 
     private boolean isSatelliteAllowed() {
         synchronized (mLock) {
-            if (isSatelliteViaCarrierAvailable()) return true;
+            if (isSatelliteConnectedViaCarrierWithinHysteresisTime()) return true;
             return mIsSatelliteAllowedForCurrentLocation;
         }
     }
@@ -338,12 +359,12 @@
      * @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;
     }
 
-    private boolean isSatelliteViaCarrierAvailable() {
+    private boolean isSatelliteConnectedViaCarrierWithinHysteresisTime() {
         return mIsSatelliteConnectedViaCarrierWithinHysteresisTime.get();
     }
 
@@ -366,13 +387,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
@@ -383,7 +403,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)
@@ -393,10 +414,13 @@
                         .setIsMultiSim(isMultiSim())
                         .setRecommendingHandoverType(getEmergencyCallToSatelliteHandoverType())
                         .setIsSatelliteAllowedInCurrentLocation(isSatelliteAllowed())
-                        .build());
+                        .setIsWifiConnected(mCountryDetector.isWifiNetworkConnected())
+                        .setCarrierId(mSatelliteController.getSatelliteCarrierId()).build());
     }
 
-    private void cleanUpResources() {
+    private void cleanUpResources(boolean isDialerNotified) {
+        plogd("cleanUpResources");
+        reportESosRecommenderDecision(isDialerNotified);
         synchronized (mLock) {
             stopTimer();
             if (mEmergencyConnection != null) {
@@ -407,12 +431,13 @@
             mIsTimerTimedOut = false;
             mCheckingAccessRestrictionInProgress = false;
             mIsSatelliteAllowedForCurrentLocation = false;
+            mIsTestEmergencyNumber = false;
         }
     }
 
     private void registerForInterestedStateChangedEvents() {
         mSatelliteController.registerForSatelliteProvisionStateChanged(
-                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, mISatelliteProvisionStateCallback);
+                mISatelliteProvisionStateCallback);
         for (Phone phone : PhoneFactory.getPhones()) {
             phone.registerForServiceStateChanged(
                     this, EVENT_SERVICE_STATE_CHANGED, null);
@@ -432,7 +457,7 @@
 
     private void unregisterForInterestedStateChangedEvents() {
         mSatelliteController.unregisterForSatelliteProvisionStateChanged(
-                SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, mISatelliteProvisionStateCallback);
+                mISatelliteProvisionStateCallback);
         for (Phone phone : PhoneFactory.getPhones()) {
             phone.unregisterForServiceStateChanged(this);
         }
@@ -450,51 +475,6 @@
         }
     }
 
-    private boolean isCellularAvailable() {
-        for (Phone phone : PhoneFactory.getPhones()) {
-            ServiceState serviceState = phone.getServiceState();
-            if (serviceState != null) {
-                int state = serviceState.getState();
-                if ((state == STATE_IN_SERVICE || state == STATE_EMERGENCY_ONLY
-                        || serviceState.isEmergencyOnly())
-                        && !isSatellitePlmn(phone.getSubId(), serviceState)) {
-                    logv("isCellularAvailable true");
-                    return true;
-                }
-            }
-        }
-        logv("isCellularAvailable false");
-        return false;
-    }
-
-    private boolean isSatellitePlmn(int subId, @NonNull ServiceState serviceState) {
-        List<String> satellitePlmnList =
-                mSatelliteController.getSatellitePlmnsForCarrier(subId);
-        if (satellitePlmnList.isEmpty()) {
-            logv("isSatellitePlmn: satellitePlmnList is empty");
-            return false;
-        }
-
-        for (NetworkRegistrationInfo nri :
-                serviceState.getNetworkRegistrationInfoListForTransportType(
-                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN)) {
-            String registeredPlmn = nri.getRegisteredPlmn();
-            String mccmnc = nri.getCellIdentity().getMccString()
-                    + nri.getCellIdentity().getMncString();
-            for (String satellitePlmn : satellitePlmnList) {
-                if (TextUtils.equals(satellitePlmn, registeredPlmn)
-                        || TextUtils.equals(satellitePlmn, mccmnc)) {
-                    logv("isSatellitePlmn: return true, satellitePlmn:" + satellitePlmn
-                            + " registeredPlmn:" + registeredPlmn + " mccmnc:" + mccmnc);
-                    return true;
-                }
-            }
-        }
-
-        logv("isSatellitePlmn: return false");
-        return false;
-    }
-
     /**
      * @return {@link ServiceState#STATE_IN_SERVICE} if any subscription is in this state; else
      * {@link ServiceState#STATE_EMERGENCY_ONLY} if any subscription is in this state; else
@@ -526,10 +506,11 @@
     }
 
     private synchronized void handleStateChangedEventForHysteresisTimer() {
-        if (!isCellularAvailable() && mEmergencyConnection != null) {
+        if (!SatelliteServiceUtils.isCellularAvailable() && mEmergencyConnection != null) {
             startTimer();
         } else {
-            logv("handleStateChangedEventForHysteresisTimer stopTimer");
+            plogd("handleStateChangedEventForHysteresisTimer stopTimer, mEmergencyConnection="
+                    + mEmergencyConnection);
             stopTimer();
         }
     }
@@ -542,7 +523,7 @@
             sendMessageDelayed(obtainMessage(EVENT_TIME_OUT), mTimeoutMillis);
             mCountOfTimerStarted++;
             mIsTimerTimedOut = false;
-            logd("startTimer mCountOfTimerStarted=" + mCountOfTimerStarted);
+            plogd("startTimer mCountOfTimerStarted=" + mCountOfTimerStarted);
         }
     }
 
@@ -561,7 +542,7 @@
     }
 
     private void selectEmergencyCallWaitForConnectionTimeoutDuration() {
-        if (mSatelliteController.isSatelliteEmergencyMessagingSupportedViaCarrier()) {
+        if (isSatelliteConnectedViaCarrierWithinHysteresisTime()) {
             mTimeoutMillis =
                     mSatelliteController.getCarrierEmergencyCallWaitForConnectionTimeoutMillis();
         } else {
@@ -610,11 +591,13 @@
 
     @Nullable
     private static String getSatelliteEmergencyHandoverIntentActionFromOverlayConfig(
-            @NonNull Context context) {
+            @NonNull Context context, boolean isTestEmergencyNumber) {
         String action;
         try {
-            action = context.getResources().getString(
-                    R.string.config_satellite_emergency_handover_intent_action);
+            int actionIntent = isTestEmergencyNumber
+                    ? R.string.config_satellite_test_with_esp_replies_intent_action
+                    : R.string.config_satellite_emergency_handover_intent_action;
+            action = context.getResources().getString(actionIntent);
         } catch (Resources.NotFoundException ex) {
             loge("getSatelliteEmergencyHandoverIntentFilterActionFromOverlayConfig: ex=" + ex);
             action = null;
@@ -660,15 +643,17 @@
         return callback;
     }
 
-    @NonNull private Bundle createExtraBundleForEventDisplayEmergencyMessage() {
+    @NonNull private Bundle createExtraBundleForEventDisplayEmergencyMessage(
+            boolean isTestEmergencyNumber) {
         int handoverType = EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
         Pair<String, String> oemSatelliteMessagingApp =
                 getOemEnabledSatelliteHandoverAppFromOverlayConfig(mContext);
         String packageName = oemSatelliteMessagingApp.first;
         String className = oemSatelliteMessagingApp.second;
-        String action = getSatelliteEmergencyHandoverIntentActionFromOverlayConfig(mContext);
+        String action = getSatelliteEmergencyHandoverIntentActionFromOverlayConfig(mContext,
+                isTestEmergencyNumber);
 
-        if (isSatelliteViaCarrierAvailable()
+        if (isSatelliteConnectedViaCarrierWithinHysteresisTime()
                 || isEmergencyCallToSatelliteHandoverTypeT911Enforced()) {
             ComponentName defaultSmsAppComponent = getDefaultSmsApp();
             handoverType = EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911;
@@ -735,7 +720,8 @@
             @NonNull Connection connection) {
         plogd("Sent EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer forcefully.");
         mEmergencyConnection = connection;
-        Bundle extras = createExtraBundleForEventDisplayEmergencyMessage();
+        Bundle extras = createExtraBundleForEventDisplayEmergencyMessage(
+                /* isTestEmergencyNumber= */ true);
         connection.sendConnectionEvent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE, extras);
         mEmergencyConnection = null;
     }
@@ -749,8 +735,14 @@
         return telephonyManager.isMultiSimEnabled();
     }
 
-    private int getEmergencyCallToSatelliteHandoverType() {
-        if (isSatelliteViaCarrierAvailable()) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public int getEmergencyCallToSatelliteHandoverType() {
+        if (Flags.carrierRoamingNbIotNtn() && isDeviceProvisioned()
+                && isSatelliteConnectedViaCarrierWithinHysteresisTime()) {
+            int satelliteSubId = mSatelliteController.getSelectedSatelliteSubId();
+            return mSatelliteController.getCarrierRoamingNtnEmergencyCallToSatelliteHandoverType(
+                    satelliteSubId);
+        } else if (isSatelliteConnectedViaCarrierWithinHysteresisTime()) {
             return EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911;
         } else {
             return EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
@@ -807,7 +799,7 @@
     }
 
     private boolean isSatelliteViaOemProvisioned() {
-        Boolean provisioned = mSatelliteController.isSatelliteViaOemProvisioned();
+        Boolean provisioned = mSatelliteController.isDeviceProvisioned();
         return (provisioned != null) && provisioned;
     }
 
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
index 4dc425d..3936a7e 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.ServiceState.STATE_EMERGENCY_ONLY;
+import static android.telephony.ServiceState.STATE_IN_SERVICE;
+
 import static java.util.stream.Collectors.joining;
 
 import android.annotation.NonNull;
@@ -24,8 +27,13 @@
 import android.os.AsyncResult;
 import android.os.Binder;
 import android.os.PersistableBundle;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.CarrierConfigManager;
+import android.telephony.CellIdentity;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.Rlog;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.satellite.AntennaPosition;
 import android.telephony.satellite.NtnSignalStrength;
@@ -33,9 +41,12 @@
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatelliteModemEnableRequestAttributes;
+import android.telephony.satellite.SatelliteSubscriptionInfo;
 import android.telephony.satellite.stub.NTRadioTechnology;
 import android.telephony.satellite.stub.SatelliteModemState;
 import android.telephony.satellite.stub.SatelliteResult;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
@@ -57,6 +68,21 @@
     private static final String TAG = "SatelliteServiceUtils";
 
     /**
+     * Converts a carrier roaming NTN (Non-Terrestrial Network) connect type constant
+     * from {@link CarrierConfigManager} to string.
+     * @param type The carrier roaming NTN connect type constant.
+     * @return A string representation of the connect type, or "Unknown(type)" if not recognized.
+     */
+    public static String carrierRoamingNtnConnectTypeToString(
+            @CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_TYPE int type) {
+        return switch (type) {
+            case CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC -> "AUTOMATIC";
+            case CarrierConfigManager.CARRIER_ROAMING_NTN_CONNECT_MANUAL -> "MANUAL";
+            default -> "Unknown(" + type + ")";
+        };
+    }
+
+    /**
      * Convert radio technology from service definition to framework definition.
      * @param radioTechnology The NTRadioTechnology from the satellite service.
      * @return The converted NTRadioTechnology for the framework.
@@ -148,9 +174,9 @@
                 return SatelliteManager.SATELLITE_MODEM_STATE_OFF;
             case SatelliteModemState.SATELLITE_MODEM_STATE_UNAVAILABLE:
                 return SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE;
-            case SatelliteModemState.SATELLITE_MODEM_STATE_NOT_CONNECTED:
+            case SatelliteModemState.SATELLITE_MODEM_STATE_OUT_OF_SERVICE:
                 return SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED;
-            case SatelliteModemState.SATELLITE_MODEM_STATE_CONNECTED:
+            case SatelliteModemState.SATELLITE_MODEM_STATE_IN_SERVICE:
                 return SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
             default:
                 loge("Received invalid modem state: " + modemState);
@@ -234,6 +260,41 @@
     }
 
     /**
+     * Convert SatelliteSubscriptionInfo from framework definition to service definition.
+     * @param info The SatelliteSubscriptionInfo from the framework.
+     * @return The converted SatelliteSubscriptionInfo for the satellite service.
+     */
+    @NonNull public static android.telephony.satellite.stub
+            .SatelliteSubscriptionInfo toSatelliteSubscriptionInfo(
+            @NonNull SatelliteSubscriptionInfo info
+    ) {
+        android.telephony.satellite.stub.SatelliteSubscriptionInfo converted =
+                new android.telephony.satellite.stub.SatelliteSubscriptionInfo();
+        converted.iccId = info.getIccId();
+        converted.niddApn = info.getNiddApn();
+        return converted;
+    }
+
+    /**
+     * Convert SatelliteModemEnableRequestAttributes from framework definition to service definition
+     * @param attributes The SatelliteModemEnableRequestAttributes from the framework.
+     * @return The converted SatelliteModemEnableRequestAttributes for the satellite service.
+     */
+    @NonNull public static android.telephony.satellite.stub
+            .SatelliteModemEnableRequestAttributes toSatelliteModemEnableRequestAttributes(
+            @NonNull SatelliteModemEnableRequestAttributes attributes
+    ) {
+        android.telephony.satellite.stub.SatelliteModemEnableRequestAttributes converted =
+                new android.telephony.satellite.stub.SatelliteModemEnableRequestAttributes();
+        converted.isEnabled = attributes.isEnabled();
+        converted.isDemoMode = attributes.isDemoMode();
+        converted.isEmergencyMode = attributes.isEmergencyMode();
+        converted.satelliteSubscriptionInfo = toSatelliteSubscriptionInfo(
+                attributes.getSatelliteSubscriptionInfo());
+        return converted;
+    }
+
+    /**
      * Get the {@link SatelliteManager.SatelliteResult} from the provided result.
      *
      * @param ar AsyncResult used to determine the error code.
@@ -282,6 +343,26 @@
     }
 
     /**
+     * Get the subscription ID which supports OEM based NTN satellite service.
+     *
+     * @return ID of the subscription that supports OEM-based satellite if any,
+     * return {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} otherwise.
+     */
+    public static int getNtnOnlySubscriptionId(@NonNull Context context) {
+        List<SubscriptionInfo> infoList =
+                SubscriptionManagerService.getInstance().getAllSubInfoList(
+                        context.getOpPackageName(), null);
+
+        int subId = infoList.stream()
+                .filter(info -> info.isOnlyNonTerrestrialNetwork())
+                .mapToInt(SubscriptionInfo::getSubscriptionId)
+                .findFirst()
+                .orElse(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        logd("getNtnOnlySubscriptionId: subId=" + subId);
+        return subId;
+    }
+
+    /**
      * Expected format of the input dictionary bundle is:
      * <ul>
      *     <li>Key: PLMN string.</li>
@@ -391,6 +472,76 @@
         return PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
     }
 
+    /** Return {@code true} if device has cellular coverage, else return {@code false}. */
+    public static boolean isCellularAvailable() {
+        for (Phone phone : PhoneFactory.getPhones()) {
+            ServiceState serviceState = phone.getServiceState();
+            if (serviceState != null) {
+                int state = serviceState.getState();
+                if ((state == STATE_IN_SERVICE || state == STATE_EMERGENCY_ONLY
+                        || serviceState.isEmergencyOnly())
+                        && !isSatellitePlmn(phone.getSubId(), serviceState)) {
+                    logd("isCellularAvailable true");
+                    return true;
+                }
+            }
+        }
+        logd("isCellularAvailable false");
+        return false;
+    }
+
+    /** Check whether device is connected to satellite PLMN */
+    public static boolean isSatellitePlmn(int subId, @NonNull ServiceState serviceState) {
+        List<String> satellitePlmnList =
+                SatelliteController.getInstance().getSatellitePlmnsForCarrier(subId);
+        if (satellitePlmnList.isEmpty()) {
+            logd("isSatellitePlmn: satellitePlmnList is empty");
+            return false;
+        }
+
+        for (NetworkRegistrationInfo nri :
+                serviceState.getNetworkRegistrationInfoListForTransportType(
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN)) {
+            String registeredPlmn = nri.getRegisteredPlmn();
+            if (TextUtils.isEmpty(registeredPlmn)) {
+                logd("isSatellitePlmn: registeredPlmn is empty");
+                continue;
+            }
+
+            String mccmnc = getMccMnc(nri);
+            for (String satellitePlmn : satellitePlmnList) {
+                if (TextUtils.equals(satellitePlmn, registeredPlmn)
+                        || TextUtils.equals(satellitePlmn, mccmnc)) {
+                    logd("isSatellitePlmn: return true, satellitePlmn:" + satellitePlmn
+                            + " registeredPlmn:" + registeredPlmn + " mccmnc:" + mccmnc);
+                    return true;
+                }
+            }
+        }
+
+        logd("isSatellitePlmn: return false");
+        return false;
+    }
+
+    /** Get mccmnc string from NetworkRegistrationInfo. */
+    @Nullable
+    public static String getMccMnc(@NonNull NetworkRegistrationInfo nri) {
+        CellIdentity cellIdentity = nri.getCellIdentity();
+        if (cellIdentity == null) {
+            logd("getMccMnc: cellIdentity is null");
+            return null;
+        }
+
+        String mcc = cellIdentity.getMccString();
+        String mnc = cellIdentity.getMncString();
+        if (mcc == null || mnc == null) {
+            logd("getMccMnc: mcc or mnc is null. mcc=" + mcc + " mnc=" + mnc);
+            return null;
+        }
+
+        return mcc + mnc;
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
@@ -398,4 +549,8 @@
     private static void loge(@NonNull String log) {
         Rlog.e(TAG, log);
     }
+
+    private static void logv(@NonNull String log) {
+        Rlog.v(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 dde10a0..1b6f9f1 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
@@ -16,6 +16,11 @@
 
 package com.android.internal.telephony.satellite;
 
+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.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;
@@ -25,23 +30,35 @@
 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_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_LISTENING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
 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.PersistentLogger;
-import android.telephony.Rlog;
+import android.telephony.ServiceState;
 import android.telephony.satellite.ISatelliteModemStateCallback;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.stub.ISatelliteGateway;
@@ -52,10 +69,16 @@
 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.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -106,10 +129,19 @@
     protected static final int EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT = 6;
     private static final int EVENT_SATELLITE_ENABLEMENT_STARTED = 7;
     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_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
     private static final int REBIND_MULTIPLIER = 2;
+    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;
+
     @NonNull private final ExponentialBackoff mExponentialBackoff;
     @NonNull private final Object mLock = new Object();
     @Nullable
@@ -118,6 +150,7 @@
     @Nullable private SatelliteGatewayServiceConnection mSatelliteGatewayServiceConnection;
     private boolean mIsBound;
     private boolean mIsBinding;
+    private boolean mIsRegisteredScreenStateChanged = false;
 
     @NonNull private static SatelliteSessionController sInstance;
 
@@ -137,16 +170,32 @@
     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;
     final boolean mIsSatelliteSupported;
     private boolean mIsDemoMode = false;
+    // Interested in screen off, so use default value true
+    boolean mIsScreenOn = true;
+    private boolean mIsDeviceAlignedWithSatellite = false;
+
     @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.
@@ -173,12 +222,28 @@
             @NonNull FeatureFlags featureFlags,
             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();
+            }
+
             sInstance = new SatelliteSessionController(
                     context,
                     looper,
                     featureFlags,
                     isSatelliteSupported,
                     SatelliteModemInterface.getInstance());
+            if (existingListeners != null) {
+                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;
     }
@@ -205,6 +270,7 @@
         }
 
         mContext = context;
+        mFeatureFlags = featureFlags;
         mSatelliteModemInterface = satelliteModemInterface;
         mSatelliteController = SatelliteController.getInstance();
         mDatagramController = DatagramController.getInstance();
@@ -215,7 +281,8 @@
                 getSatelliteNbIotInactivityTimeoutMillis();
         mListeners = new ConcurrentHashMap<>();
         mIsSendingTriggeredDuringTransferringState = new AtomicBoolean(false);
-        mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
+        mPreviousState = SATELLITE_MODEM_STATE_UNKNOWN;
+        mCurrentState = SATELLITE_MODEM_STATE_UNKNOWN;
         mIsSatelliteSupported = isSatelliteSupported;
         mExponentialBackoff = new ExponentialBackoff(REBIND_INITIAL_DELAY, REBIND_MAXIMUM_DELAY,
                 REBIND_MULTIPLIER, looper, () -> {
@@ -234,6 +301,14 @@
             bindService();
         });
 
+        Phone satellitePhone = mSatelliteController.getSatellitePhone();
+        if (satellitePhone == null) {
+            satellitePhone = SatelliteServiceUtils.getPhone();
+        }
+        mDeviceStateMonitor = satellitePhone.getDeviceStateMonitor();
+        mSessionMetricsStats = SessionMetricsStats.getInstance();
+        mAlarmManager = mContext.getSystemService(AlarmManager.class);
+
         addState(mUnavailableState);
         addState(mPowerOffState);
         addState(mEnablingState);
@@ -257,9 +332,10 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void onDatagramTransferStateChanged(
             @SatelliteManager.SatelliteDatagramTransferState int sendState,
-            @SatelliteManager.SatelliteDatagramTransferState int receiveState) {
+            @SatelliteManager.SatelliteDatagramTransferState int receiveState,
+            @SatelliteManager.DatagramType int datagramType) {
         sendMessage(EVENT_DATAGRAM_TRANSFER_STATE_CHANGED,
-                new DatagramTransferState(sendState, receiveState));
+                new DatagramTransferState(sendState, receiveState, datagramType));
         if (sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING) {
             mIsSendingTriggeredDuringTransferringState.set(true);
         }
@@ -290,10 +366,12 @@
     /**
      * {@link SatelliteController} uses this function to notify {@link SatelliteSessionController}
      * that the satellite enablement has just failed.
+     *
+     * @param enabled {@code true} if satellite is enabled, {@code false} satellite is disabled.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public void onSatelliteEnablementFailed() {
-        sendMessage(EVENT_SATELLITE_ENABLEMENT_FAILED);
+    public void onSatelliteEnablementFailed(boolean enabled) {
+        sendMessage(EVENT_SATELLITE_ENABLEMENT_FAILED, enabled);
     }
 
     /**
@@ -308,6 +386,35 @@
     }
 
     /**
+     * {@link SatelliteController} uses this function to notify {@link SatelliteSessionController}
+     * that the satellite emergency mode has changed.
+     *
+     * @param isEmergencyMode The satellite emergency mode.
+     */
+    public void onEmergencyModeChanged(boolean isEmergencyMode) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("onEmergencyModeChanged: carrierRoamingNbIotNtn is disabled");
+            return;
+        }
+
+        plogd("onEmergencyModeChanged " + isEmergencyMode);
+
+        List<ISatelliteModemStateCallback> toBeRemoved = new ArrayList<>();
+        mListeners.values().forEach(listener -> {
+            try {
+                listener.onEmergencyModeChanged(isEmergencyMode);
+            } catch (RemoteException e) {
+                plogd("onEmergencyModeChanged RemoteException: " + e);
+                toBeRemoved.add(listener);
+            }
+        });
+
+        toBeRemoved.forEach(listener -> {
+            mListeners.remove(listener.asBinder());
+        });
+    }
+
+    /**
      * Registers for modem state changed from satellite modem.
      *
      * @param callback The callback to handle the satellite modem state changed event.
@@ -316,6 +423,9 @@
             @NonNull ISatelliteModemStateCallback callback) {
         try {
             callback.onSatelliteModemStateChanged(mCurrentState);
+            if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+                callback.onEmergencyModeChanged(mSatelliteController.getRequestIsEmergency());
+            }
             mListeners.put(callback.asBinder(), callback);
         } catch (RemoteException ex) {
             ploge("registerForSatelliteModemStateChanged: Got RemoteException ex=" + ex);
@@ -366,6 +476,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.
@@ -413,6 +540,31 @@
     }
 
     /**
+     * Notify whether the device is aligned with the satellite
+     *
+     * @param isAligned {@code true} Device is aligned with the satellite,
+     *                  {@code false} otherwise.
+     */
+    public void setDeviceAlignedWithSatellite(boolean isAligned) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("setDeviceAlignedWithSatellite: carrierRoamingNbIotNtn is disabled");
+            return;
+        }
+
+        mIsDeviceAlignedWithSatellite = isAligned;
+
+        if (mIsDeviceAlignedWithSatellite) {
+            stopEsosInactivityTimer();
+            stopP2pSmsInactivityTimer();
+        } else {
+            if (mCurrentState == SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED) {
+                evaluateStartingEsosInactivityTimer();
+                evaluateStartingP2pSmsInactivityTimer();
+            }
+        }
+    }
+
+    /**
      * Get whether state machine is in enabling state.
      *
      * @return {@code true} if state machine is in enabling state and {@code false} otherwise.
@@ -422,6 +574,64 @@
         return getCurrentState() == mEnablingState;
     }
 
+    /**
+     * Get whether state machine is in disabling state.
+     *
+     * @return {@code true} if state machine is in disabling state and {@code false} otherwise.
+     */
+    public boolean isInDisablingState() {
+        if (DBG) plogd("isInDisablingState: getCurrentState=" + getCurrentState());
+        return getCurrentState() == mDisablingState;
+    }
+
+    /**
+     * Release all resource.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public void cleanUpResource() {
+        plogd("cleanUpResource");
+        mIsDeviceAlignedWithSatellite = false;
+        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;
     }
@@ -429,11 +639,14 @@
     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;
         }
     }
 
@@ -441,6 +654,7 @@
         @Override
         public void enter() {
             if (DBG) plogd("Entering UnavailableState");
+            mPreviousState = mCurrentState;
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE;
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE);
         }
@@ -457,6 +671,9 @@
         public void enter() {
             if (DBG) plogd("Entering PowerOffState");
 
+            mSatelliteController.moveSatelliteToOffStateAndCleanUpResources(
+                    SATELLITE_RESULT_REQUEST_ABORTED);
+            mPreviousState = mCurrentState;
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_OFF;
             mIsSendingTriggeredDuringTransferringState.set(false);
             synchronized (mLock) {
@@ -466,6 +683,8 @@
             stopNbIotInactivityTimer();
             DemoSimulator.getInstance().onSatelliteModeOff();
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+
+            unregisterForScreenStateChanged();
         }
 
         @Override
@@ -501,8 +720,9 @@
         public void enter() {
             if (DBG) plogd("Entering EnablingState");
 
-            mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_ENABLING_SATELLITE;
-            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_ENABLING_SATELLITE);
+            mPreviousState = mCurrentState;
+            mCurrentState = SATELLITE_MODEM_STATE_ENABLING_SATELLITE;
+            notifyStateChangedEvent(SATELLITE_MODEM_STATE_ENABLING_SATELLITE);
         }
 
         @Override
@@ -518,10 +738,17 @@
                     handleSatelliteEnabledStateChanged((boolean) msg.obj);
                     break;
                 case EVENT_SATELLITE_ENABLEMENT_FAILED:
-                    transitionTo(mPowerOffState);
+                    if ((boolean) msg.obj) {
+                        transitionTo(mPowerOffState);
+                    } else {
+                        ploge("Unexpected failed disable event in EnablingState");
+                    }
                     break;
                 case EVENT_SATELLITE_MODEM_STATE_CHANGED:
-                    deferMessage(msg);
+                    handleSatelliteModemStateChanged(msg);
+                    break;
+                case EVENT_SATELLITE_ENABLEMENT_STARTED:
+                    handleSatelliteEnablementStarted((boolean) msg.obj);
                     break;
             }
             // Ignore all unexpected events.
@@ -536,6 +763,8 @@
                     transitionTo(mIdleState);
                 }
                 DemoSimulator.getInstance().onSatelliteModeOn();
+
+                registerForScreenStateChanged();
             } else {
                 /*
                  * During the state transition from ENABLING to NOT_CONNECTED, modem might be
@@ -547,6 +776,15 @@
                 transitionTo(mPowerOffState);
             }
         }
+
+        private void handleSatelliteModemStateChanged(@NonNull Message msg) {
+            int state = msg.arg1;
+            if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF) {
+                transitionTo(mPowerOffState);
+            } else {
+                deferMessage(msg);
+            }
+        }
     }
 
     private class DisablingState extends State {
@@ -554,8 +792,11 @@
         public void enter() {
             if (DBG) plogd("Entering DisablingState");
 
+            mPreviousState = mCurrentState;
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_DISABLING_SATELLITE;
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_DISABLING_SATELLITE);
+
+            unregisterForScreenStateChanged();
         }
 
         @Override
@@ -570,6 +811,32 @@
                 case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
                     handleSatelliteEnabledStateChanged((boolean) msg.obj);
                     break;
+                case EVENT_SATELLITE_ENABLEMENT_FAILED:
+                    boolean enablingSatellite = (boolean) msg.obj;
+                    if (enablingSatellite) {
+                        /* Framework received a disable request when the enable request was in
+                         * progress. We need to set the previous state to OFF since the enable has
+                         * failed so that when disable fail, we can move to OFF state properly.
+                         */
+                        mPreviousState = SatelliteManager.SATELLITE_MODEM_STATE_OFF;
+                        plogd("Enable request has failed. Set mPreviousState to OFF");
+                        break;
+                    }
+                    if (mPreviousState == SATELLITE_MODEM_STATE_CONNECTED
+                            || mPreviousState == SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING
+                            || mPreviousState == SATELLITE_MODEM_STATE_LISTENING) {
+                        transitionTo(mConnectedState);
+                    } else if (mPreviousState == SATELLITE_MODEM_STATE_ENABLING_SATELLITE) {
+                        transitionTo(mEnablingState);
+                    } else if (mPreviousState == SatelliteManager.SATELLITE_MODEM_STATE_OFF) {
+                        transitionTo(mPowerOffState);
+                    } else {
+                        transitionTo(mNotConnectedState);
+                    }
+                    break;
+                case EVENT_SATELLITE_MODEM_STATE_CHANGED:
+                    handleEventSatelliteModemStateChanged(msg);
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -577,22 +844,42 @@
 
         private void handleSatelliteEnabledStateChanged(boolean on) {
             if (on) {
-                plogw("Unexpected power on event while disabling satellite");
+                /* Framework received a disable request when the enable request was in progress.
+                 * We need to set the previous state to NOT_CONNECTED since the enable has
+                 * succeeded so that when disable fail, we can move to NOT_CONNECTED state properly.
+                 */
+                mPreviousState = SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED;
+                plogd("Enable request has succeeded. Set mPreviousState to NOT_CONNECTED");
             } else {
                 transitionTo(mPowerOffState);
             }
         }
+
+        private void handleEventSatelliteModemStateChanged(@NonNull Message msg) {
+            int state = msg.arg1;
+            if (state == SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED) {
+                mPreviousState = state;
+            } else if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF) {
+                plogd("Modem OFF state will be processed after getting the confirmation of the"
+                        + " disable request");
+                deferMessage(msg);
+            }
+        }
     }
 
     private class IdleState extends State {
         @Override
         public void enter() {
             if (DBG) plogd("Entering IdleState");
+            mPreviousState = mCurrentState;
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_IDLE;
             mIsSendingTriggeredDuringTransferringState.set(false);
             stopNbIotInactivityTimer();
+
             //Enable Cellular Modem scanning
-            mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(true, null);
+            Message onCompleted =
+                    obtainMessage(EVENT_ENABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE);
+            mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(true, onCompleted);
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
         }
 
@@ -613,6 +900,31 @@
                 case EVENT_SATELLITE_ENABLEMENT_STARTED:
                     handleSatelliteEnablementStarted((boolean) msg.obj);
                     break;
+                case EVENT_SCREEN_STATE_CHANGED:
+                    handleEventScreenStateChanged((AsyncResult) msg.obj);
+                    break;
+                case EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT:
+                    handleEventScreenOffInactivityTimerTimedOut();
+                    break;
+                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;
@@ -640,6 +952,45 @@
             }
         }
 
+        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) {
             synchronized (mLock) {
@@ -656,6 +1007,13 @@
             }
         }
 
+        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) {
@@ -686,8 +1044,9 @@
         public void enter() {
             if (DBG) plogd("Entering TransferringState");
             stopNbIotInactivityTimer();
-            mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
-            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+            mPreviousState = mCurrentState;
+            mCurrentState = SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
+            notifyStateChangedEvent(SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
         }
 
         @Override
@@ -706,6 +1065,12 @@
                 case EVENT_SATELLITE_ENABLEMENT_STARTED:
                     handleSatelliteEnablementStarted((boolean) msg.obj);
                     break;
+                case EVENT_SCREEN_STATE_CHANGED:
+                    handleEventScreenStateChanged((AsyncResult) msg.obj);
+                    break;
+                case EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT:
+                    handleEventScreenOffInactivityTimerTimedOut();
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -736,6 +1101,8 @@
                 @SatelliteManager.SatelliteModemState int state) {
             if (state == SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED) {
                 transitionTo(mNotConnectedState);
+            } else if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF) {
+                transitionTo(mPowerOffState);
             }
         }
     }
@@ -745,11 +1112,12 @@
         public void enter() {
             if (DBG) plogd("Entering ListeningState");
 
-            mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_LISTENING;
+            mPreviousState = mCurrentState;
+            mCurrentState = SATELLITE_MODEM_STATE_LISTENING;
             long timeoutMillis = updateListeningMode(true);
             sendMessageDelayed(EVENT_LISTENING_TIMER_TIMEOUT, timeoutMillis);
             mIsSendingTriggeredDuringTransferringState.set(false);
-            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_LISTENING);
+            notifyStateChangedEvent(SATELLITE_MODEM_STATE_LISTENING);
         }
 
         @Override
@@ -776,6 +1144,15 @@
                 case EVENT_SATELLITE_ENABLEMENT_STARTED:
                     handleSatelliteEnablementStarted((boolean) msg.obj);
                     break;
+                case EVENT_SCREEN_STATE_CHANGED:
+                    handleEventScreenStateChanged((AsyncResult) msg.obj);
+                    break;
+                case EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT:
+                    handleEventScreenOffInactivityTimerTimedOut();
+                    break;
+                case EVENT_SATELLITE_MODEM_STATE_CHANGED:
+                    handleEventSatelliteModemStateChange(msg);
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -801,6 +1178,13 @@
                 transitionTo(mTransferringState);
             }
         }
+
+        private void handleEventSatelliteModemStateChange(@NonNull Message msg) {
+            int state = msg.arg1;
+            if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF) {
+                transitionTo(mPowerOffState);
+            }
+        }
     }
 
     private class NotConnectedState extends State {
@@ -808,14 +1192,20 @@
         public void enter() {
             if (DBG) plogd("Entering NotConnectedState");
 
+            mPreviousState = mCurrentState;
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED;
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
             startNbIotInactivityTimer();
+            evaluateStartingEsosInactivityTimer();
+            evaluateStartingP2pSmsInactivityTimer();
         }
 
         @Override
         public void exit() {
             if (DBG) plogd("Exiting NotConnectedState");
+
+            stopEsosInactivityTimer();
+            stopP2pSmsInactivityTimer();
         }
 
         @Override
@@ -829,6 +1219,22 @@
                 case EVENT_SATELLITE_MODEM_STATE_CHANGED:
                     handleEventSatelliteModemStateChanged(msg.arg1);
                     break;
+                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:
+                    if (isEsosInActivityTimerStarted()) {
+                        plogd("NotConnectedState: processing: ESOS inactivity timer running "
+                                + "can not move to IDLE");
+                    } else {
+                        transitionTo(mIdleState);
+                    }
+                    break;
                 case EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT:
                     transitionTo(mIdleState);
                     break;
@@ -838,6 +1244,12 @@
                 case EVENT_SATELLITE_ENABLEMENT_STARTED:
                     handleSatelliteEnablementStarted((boolean) msg.obj);
                     break;
+                case EVENT_SCREEN_STATE_CHANGED:
+                    handleEventScreenStateChanged((AsyncResult) msg.obj);
+                    break;
+                case EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT:
+                    handleEventScreenOffInactivityTimerTimedOut();
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -845,8 +1257,10 @@
 
         private void handleEventSatelliteModemStateChanged(
                 @SatelliteManager.SatelliteModemState int state) {
-            if (state == SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED) {
+            if (state == SATELLITE_MODEM_STATE_CONNECTED) {
                 transitionTo(mConnectedState);
+            } else if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF) {
+                transitionTo(mPowerOffState);
             }
         }
 
@@ -857,13 +1271,29 @@
                     || datagramTransferState.receiveState
                     == SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT) {
                 stopNbIotInactivityTimer();
+
+                if (mSatelliteController.getRequestIsEmergency()) {
+                    stopEsosInactivityTimer();
+                }
+                stopP2pSmsInactivityTimer();
             } else if (datagramTransferState.sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE
                     && datagramTransferState.receiveState
                     == SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE) {
                 startNbIotInactivityTimer();
+                evaluateStartingEsosInactivityTimer();
+                evaluateStartingP2pSmsInactivityTimer();
             } else if (isSending(datagramTransferState.sendState)
                     || isReceiving(datagramTransferState.receiveState)) {
-                restartNbIotInactivityTimer();
+                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);
+                }
             }
         }
     }
@@ -873,14 +1303,20 @@
         public void enter() {
             if (DBG) plogd("Entering ConnectedState");
 
-            mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
-            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+            mPreviousState = mCurrentState;
+            mCurrentState = SATELLITE_MODEM_STATE_CONNECTED;
+            notifyStateChangedEvent(SATELLITE_MODEM_STATE_CONNECTED);
             startNbIotInactivityTimer();
+            evaluateStartingEsosInactivityTimer();
+            evaluateStartingP2pSmsInactivityTimer();
         }
 
         @Override
         public void exit() {
             if (DBG) plogd("Exiting ConnectedState");
+
+            stopEsosInactivityTimer();
+            stopP2pSmsInactivityTimer();
         }
 
         @Override
@@ -903,6 +1339,28 @@
                 case EVENT_SATELLITE_ENABLEMENT_STARTED:
                     handleSatelliteEnablementStarted((boolean) msg.obj);
                     break;
+                case EVENT_SCREEN_STATE_CHANGED:
+                    handleEventScreenStateChanged((AsyncResult) msg.obj);
+                    break;
+                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:
+                    if (isEsosInActivityTimerStarted()) {
+                        plogd("ConnectedState: processing: ESOS inactivity timer running "
+                                + "can not move to IDLE");
+                    } else {
+                        transitionTo(mIdleState);
+                    }
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -912,6 +1370,8 @@
                 @SatelliteManager.SatelliteModemState int state) {
             if (state == SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED) {
                 transitionTo(mNotConnectedState);
+            } else if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF) {
+                transitionTo(mPowerOffState);
             }
         }
 
@@ -955,6 +1415,24 @@
             case EVENT_SATELLITE_ENABLEMENT_FAILED:
                 whatString = "EVENT_SATELLITE_ENABLEMENT_FAILED";
                 break;
+            case EVENT_SCREEN_STATE_CHANGED:
+                whatString = "EVENT_SCREEN_STATE_CHANGED";
+                break;
+            case EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT:
+                whatString = "EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT";
+                break;
+            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;
         }
@@ -969,6 +1447,10 @@
         }
     }
 
+    private int getSubId() {
+        return mSatelliteController.getSelectedSatelliteSubId();
+    }
+
     private void notifyStateChangedEvent(@SatelliteManager.SatelliteModemState int state) {
         mDatagramController.onSatelliteModemStateChanged(state);
 
@@ -1074,6 +1556,7 @@
             mSatelliteGatewayServiceConnection = null;
         }
     }
+
     private class SatelliteGatewayServiceConnection implements ServiceConnection {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
@@ -1114,6 +1597,239 @@
         }
     }
 
+    private void registerForScreenStateChanged() {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            Rlog.d(TAG, "registerForScreenStateChanged: carrierRoamingNbIotNtn is disabled");
+            return;
+        }
+
+        if (!mSatelliteController.isInCarrierRoamingNbIotNtn()) {
+            logd("registerScreenOnOffChanged: device is not in CarrierRoamingNbIotNtn");
+            return;
+        }
+
+        if (mSatelliteController.getRequestIsEmergency()) {
+            logd("registerScreenOnOffChanged: not register, device is in 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);
+
+            mIsRegisteredScreenStateChanged = true;
+            plogd("registerForScreenStateChanged: registered");
+        } else {
+            plogw("registerForScreenStateChanged: skip register, mIsRegisteredScreenStateChanged="
+                    + mIsRegisteredScreenStateChanged + ","
+                    + " mDeviceStateMonitor=" + (mDeviceStateMonitor != null));
+        }
+    }
+
+    private void unregisterForScreenStateChanged() {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            Rlog.d(TAG, "unregisterForScreenStateChanged: carrierRoamingNbIotNtn is disabled");
+            return;
+        }
+
+        if (mIsRegisteredScreenStateChanged && mDeviceStateMonitor != null) {
+            mDeviceStateMonitor.unregisterForScreenStateChanged(getHandler());
+            removeMessages(EVENT_SCREEN_STATE_CHANGED);
+            removeMessages(EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT);
+
+            removeDeferredMessages(EVENT_SCREEN_STATE_CHANGED);
+            removeDeferredMessages(EVENT_SCREEN_OFF_INACTIVITY_TIMER_TIMED_OUT);
+
+            mIsRegisteredScreenStateChanged = false;
+            plogd("unregisterForScreenStateChanged: unregistered");
+        }
+    }
+
+    private void handleEventScreenStateChanged(AsyncResult asyncResult) {
+        if (asyncResult == null) {
+            ploge("handleEventScreenStateChanged: asyncResult is null");
+            return;
+        }
+
+        boolean screenOn = (boolean) asyncResult.result;
+        if (mIsScreenOn == screenOn) {
+            if (DBG) plogd("handleEventScreenStateChanged: screen state is not changed");
+            return;
+        }
+        mIsScreenOn = screenOn;
+
+        if (!screenOn) {
+            // Screen off, start timer
+            int timeoutMillis = getScreenOffInactivityTimeoutDurationSec() * 1000;
+
+            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 int getScreenOffInactivityTimeoutDurationSec() {
+        PersistableBundle config = mSatelliteController.getPersistableBundle(getSubId());
+
+        return config.getInt(KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT,
+                DEFAULT_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC);
+    }
+
+    private int getEsosInactivityTimeoutDurationSec() {
+        PersistableBundle config = mSatelliteController.getPersistableBundle(getSubId());
+
+        return config.getInt(KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT,
+                DEFAULT_ESOS_INACTIVITY_TIMEOUT_SEC);
+    }
+
+    private void evaluateStartingEsosInactivityTimer() {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            plogd("evaluateStartingEsosInactivityTimer: "
+                    + "carrierRoamingNbIotNtn is disabled");
+            return;
+        }
+
+        if (isEsosInActivityTimerStarted()) {
+            plogd("isEsosInActivityTimerStarted: "
+                    + "ESOS inactivity timer already started");
+            return;
+        }
+
+        int subId = getSubId();
+        if (!mSatelliteController.isSatelliteEsosSupported(subId)) {
+            plogd("evaluateStartingEsosInactivityTimer: ESOS is not supported");
+            return;
+        }
+
+        if (!mSatelliteController.getRequestIsEmergency()) {
+            plogd("evaluateStartingEsosInactivityTimer: request is not emergency");
+            return;
+        }
+
+        if (mIsDeviceAlignedWithSatellite) {
+            plogd("evaluateStartingEsosInactivityTimer: "
+                    + "can't start ESOS inactivity timer due to device aligned satellite");
+            return;
+        }
+
+        int timeOutMillis = getEsosInactivityTimeoutDurationSec() * 1000;
+        DatagramController datagramController = DatagramController.getInstance();
+        if (datagramController.isSendingInIdleState()
+                && datagramController.isPollingInIdleState()) {
+            sendMessageDelayed(EVENT_ESOS_INACTIVITY_TIMER_TIMED_OUT, timeOutMillis);
+            plogd("evaluateStartingEsosInactivityTimer: start ESOS inactivity timer "
+                    + timeOutMillis);
+        } else {
+            plogd("evaluateStartingEsosInactivityTimer: "
+                    + "can't start ESOS inactivity timer");
+        }
+    }
+
+    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("isEsosInActivityTimerStarted: "
+                    + "P2P_SMS inactivity timer already started");
+            return;
+        }
+
+        int subId = getSubId();
+        if (!mSatelliteController.isSatelliteRoamingP2pSmSSupported(subId)) {
+            plogd("evaluateStartingEsosInactivityTimer: P2P_SMS is not supported");
+            return;
+        }
+
+        if (mIsDeviceAlignedWithSatellite) {
+            plogd("evaluateStartingEsosInactivityTimer: "
+                    + "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("evaluateStartingEsosInactivityTimer: start P2P_SMS inactivity timer "
+                    + timeOutMillis);
+        } else {
+            plogd("evaluateStartingEsosInactivityTimer: "
+                    + "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);
+    }
+
+    private void handleEventScreenOffInactivityTimerTimedOut() {
+        plogd("handleEventScreenOffInactivityTimerTimedOut: 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);
+                        if (result == SATELLITE_RESULT_SUCCESS) {
+                            mSessionMetricsStats.addCountOfAutoExitDueToScreenOff();
+                        }
+                    }
+                });
+    }
+
     private boolean isMockModemAllowed() {
         return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false));
     }
@@ -1152,6 +1868,12 @@
     }
 
     private void startNbIotInactivityTimer() {
+        if (!isSatelliteEnabledForNtnOnlySubscription()) {
+            plogd("startNbIotInactivityTimer: Can't start timer "
+                    + "because satellite was not enabled for OEM based NB IOT");
+            return;
+        }
+
         if (isNbIotInactivityTimerStarted()) {
             plogd("NB IOT inactivity timer is already started");
             return;
@@ -1174,6 +1896,18 @@
         return hasMessages(EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT);
     }
 
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected boolean isSatelliteEnabledForNtnOnlySubscription() {
+        if (SatelliteServiceUtils.getNtnOnlySubscriptionId(mContext)
+                != getSubId()) {
+            plogd("isSatelliteEnabledForOemBasedNbIot: highest priority satellite subscription "
+                    + "is not NTN-only subscription");
+            return false;
+        }
+
+        return true;
+    }
+
     private boolean isSatellitePersistentLoggingEnabled(
             @NonNull Context context, @NonNull FeatureFlags featureFlags) {
         if (featureFlags.satellitePersistentLogging()) {
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 13ba709..4333253 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java
@@ -15,9 +15,12 @@
  */
 package com.android.internal.telephony.satellite.metrics;
 
+import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
+import static com.android.internal.telephony.satellite.SatelliteConstants.ACCESS_CONTROL_TYPE_UNKNOWN;
 import static com.android.internal.telephony.satellite.SatelliteConstants.CONFIG_DATA_SOURCE_UNKNOWN;
+import static com.android.internal.telephony.satellite.SatelliteConstants.TRIGGERING_EVENT_UNKNOWN;
 
 import android.annotation.NonNull;
 import android.telephony.satellite.SatelliteManager;
@@ -42,6 +45,8 @@
     private @SatelliteManager.SatelliteResult int mResultCode;
     private String[] mCountryCodes;
     private @SatelliteConstants.ConfigDataSource int mConfigDataSource;
+    private int mCarrierId;
+    private @SatelliteConstants.TriggeringEvent int mTriggeringEvent;
     private AccessControllerMetricsStats() {
         initializeAccessControllerMetricsParam();
     }
@@ -61,7 +66,7 @@
         return sInstance;
     }
     private void initializeAccessControllerMetricsParam() {
-        mAccessControlType = SatelliteConstants.ACCESS_CONTROL_TYPE_UNKNOWN;
+        mAccessControlType = ACCESS_CONTROL_TYPE_UNKNOWN;
         mLocationQueryTimeMillis = 0;
         mOnDeviceLookupTimeMillis = 0;
         mTotalCheckingTimeMillis = 0;
@@ -70,6 +75,8 @@
         mResultCode = SATELLITE_RESULT_SUCCESS;
         mCountryCodes = new String[0];
         mConfigDataSource = CONFIG_DATA_SOURCE_UNKNOWN;
+        mCarrierId = UNKNOWN_CARRIER_ID;
+        mTriggeringEvent = TRIGGERING_EVENT_UNKNOWN;
     }
     /**
      * Sets the Access Control Type for current satellite enablement.
@@ -161,6 +168,26 @@
         logd("setConfigDataSource: config data source = " + mConfigDataSource);
         return this;
     }
+    /**
+     * Sets the carrier id for NTN satellite service.
+     * @param carrierId Carrier ID of currently available NTN Satellite Network.
+     */
+    public AccessControllerMetricsStats setCarrierId(int carrierId) {
+        mCarrierId = carrierId;
+        logd("setCarrierId: Carrier ID = " + mCarrierId);
+        return this;
+    }
+    /**
+     * Sets the triggering event for satellite access controller operation.
+     * @param triggeringEvent triggering event.
+     */
+    public AccessControllerMetricsStats setTriggeringEvent(
+            @SatelliteConstants.TriggeringEvent int triggeringEvent) {
+        mTriggeringEvent = triggeringEvent;
+        logd("setTriggeringEvent: triggering event = " + mTriggeringEvent);
+        return this;
+    }
+
     /** Report the access controller metrics atoms to PersistAtomsStorage in telephony. */
     public void reportAccessControllerMetrics() {
         SatelliteStats.SatelliteAccessControllerParams accessControllerParams =
@@ -174,6 +201,8 @@
                         .setResult(mResultCode)
                         .setCountryCodes(mCountryCodes)
                         .setConfigDatasource(mConfigDataSource)
+                        .setCarrierId(mCarrierId)
+                        .setTriggeringEvent(mTriggeringEvent)
                         .build();
         logd("reportAccessControllerMetrics: " + accessControllerParams.toString());
         SatelliteStats.getInstance().onSatelliteAccessControllerMetrics(accessControllerParams);
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteControllerStats.java b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteControllerStats.java
index 9524b75..e97d234 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteControllerStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteControllerStats.java
@@ -80,6 +80,22 @@
                         .build());
     }
 
+    /** Capture the NB-IoT NTN carrier ID */
+    public void reportCarrierId(int carrierId) {
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(
+                new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                        .setCarrierId(carrierId)
+                        .build());
+    }
+
+    /** Capture whether the device is satellite entitled or not */
+    public void reportIsDeviceEntitled(boolean isDeviceEntitled) {
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(
+                new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                        .setIsDeviceEntitled(isDeviceEntitled)
+                        .build());
+    }
+
     private static void logd(@NonNull String log) {
         Log.d(TAG, log);
     }
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 7dff2e6..56c3431 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
@@ -141,14 +141,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 +165,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();
@@ -371,6 +369,54 @@
         }
     }
 
+    /** Capture the latest provisioned state for satellite service */
+    @VisibleForTesting
+    public void setIsProvisioned(boolean isProvisioned) {
+        logd("setIsProvisioned:" + isProvisioned);
+        mSatelliteStats.onSatelliteControllerMetrics(
+                new SatelliteStats.SatelliteControllerParams.Builder()
+                        .setIsProvisioned(isProvisioned)
+                        .build());
+    }
+
+    /** Capture the NB-IoT NTN carrier ID */
+    @VisibleForTesting
+    public void setCarrierId(int carrierId) {
+        logd("setCarrierId:" + carrierId);
+        mSatelliteStats.onSatelliteControllerMetrics(
+                new SatelliteStats.SatelliteControllerParams.Builder()
+                        .setCarrierId(carrierId)
+                        .build());
+    }
+
+    /**
+     * Report a counter when allowed state has changed.
+     */
+    public void reportAllowedStateChanged() {
+        logd("reportAllowedStateChanged:");
+        mSatelliteStats.onSatelliteControllerMetrics(
+                new SatelliteStats.SatelliteControllerParams.Builder()
+                        .setCountOfSatelliteAllowedStateChangedEvents(ADD_COUNT)
+                        .build());
+    }
+
+    /**
+     * Report a counter when location query was successful or failed.
+     */
+    public void reportLocationQuerySuccessful(boolean result) {
+        SatelliteStats.SatelliteControllerParams.Builder builder;
+        if (result) {
+            builder = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfSuccessfulLocationQueries(ADD_COUNT);
+        } else {
+            builder = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfFailedLocationQueries(ADD_COUNT);
+        }
+        SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
+        logd("reportLocationQuerySuccessful:" + controllerParam);
+        mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
+    }
+
     /** 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;
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 0647231..73be042 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.satellite.metrics;
 
+import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
+
 import android.annotation.NonNull;
 import android.telephony.satellite.SatelliteManager;
 import android.util.Log;
@@ -37,6 +39,7 @@
     private int mProvisioningStartTimeSec;
     private boolean mIsProvisionRequest;
     private boolean mIsCanceled;
+    private int mCarrierId;
 
     private ProvisionMetricsStats() {
         initializeProvisionParams();
@@ -80,6 +83,12 @@
         return this;
     }
 
+    /** Sets the Carrier of NTN satellite */
+    public ProvisionMetricsStats setCarrierId(int carrierId) {
+        mCarrierId = carrierId;
+        return this;
+    }
+
     /** Report the provision metrics atoms to PersistAtomsStorage in telephony */
     public void reportProvisionMetrics() {
         SatelliteStats.SatelliteProvisionParams provisionParams =
@@ -89,9 +98,10 @@
                                 (System.currentTimeMillis() / 1000) - mProvisioningStartTimeSec)
                         .setIsProvisionRequest(mIsProvisionRequest)
                         .setIsCanceled(mIsCanceled)
+                        .setCarrierId(mCarrierId)
                         .build();
         SatelliteStats.getInstance().onSatelliteProvisionMetrics(provisionParams);
-        logd("reportProvisionMetrics: " + provisionParams.toString());
+        logd("reportProvisionMetrics: " + provisionParams);
         initializeProvisionParams();
     }
 
@@ -100,6 +110,7 @@
         mProvisioningStartTimeSec = INVALID_TIME;
         mIsProvisionRequest = false;
         mIsCanceled = false;
+        mCarrierId = UNKNOWN_CARRIER_ID;
     }
 
     private static void logd(@NonNull String 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 65181c0..a234378 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.satellite.metrics;
 
+import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
@@ -52,6 +53,10 @@
     private int mCountOfIncomingDatagramFailed;
     private boolean mIsDemoMode;
     private @NtnSignalStrength.NtnSignalStrengthLevel int mMaxNtnSignalStrengthLevel;
+    private int mCarrierId;
+    private int mCountOfSatelliteNotificationDisplayed;
+    private int mCountOfAutoExitDueToScreenOff;
+    private int mCountOfAutoExitDueToTnNetwork;
 
     private SessionMetricsStats() {
         initializeSessionMetricsParam();
@@ -213,6 +218,35 @@
         return this;
     }
 
+    /** Sets the Carrier ID of this NTN session. */
+    public SessionMetricsStats setCarrierId(int carrierId) {
+        mCarrierId = carrierId;
+        logd("setCarrierId(" + carrierId + ")");
+        return this;
+    }
+
+    /** Increase the count of Satellite Notification Display. */
+    public SessionMetricsStats addCountOfSatelliteNotificationDisplayed() {
+        mCountOfSatelliteNotificationDisplayed++;
+        logd("addCountOfSatelliteNotificationDisplayed: current count="
+                + mCountOfSatelliteNotificationDisplayed);
+        return this;
+    }
+
+    /** Increase the count of auto exit from P2P satellite messaging due to screen off. */
+    public SessionMetricsStats addCountOfAutoExitDueToScreenOff() {
+        mCountOfAutoExitDueToScreenOff++;
+        logd("addCountOfAutoExitDueToScreenOff: current count=" + mCountOfAutoExitDueToScreenOff);
+        return this;
+    }
+
+    /** Increase the count of auto exit from P2P satellite messaging due to scan TN network. */
+    public SessionMetricsStats addCountOfAutoExitDueToTnNetwork() {
+        mCountOfAutoExitDueToTnNetwork++;
+        logd("addCountOfAutoExitDueToTnNetwork: current count=" + mCountOfAutoExitDueToTnNetwork);
+        return this;
+    }
+
     /** Report the session metrics atoms to PersistAtomsStorage in telephony. */
     public void reportSessionMetrics() {
         SatelliteStats.SatelliteSessionParams sessionParams =
@@ -229,6 +263,11 @@
                         .setCountOfIncomingDatagramFailed(mCountOfIncomingDatagramFailed)
                         .setIsDemoMode(mIsDemoMode)
                         .setMaxNtnSignalStrengthLevel(mMaxNtnSignalStrengthLevel)
+                        .setCarrierId(mCarrierId)
+                        .setCountOfSatelliteNotificationDisplayed(
+                                mCountOfSatelliteNotificationDisplayed)
+                        .setCountOfAutoExitDueToScreenOff(mCountOfAutoExitDueToScreenOff)
+                        .setCountOfAutoExitDueToTnNetwork(mCountOfAutoExitDueToTnNetwork)
                         .build();
         logd("reportSessionMetrics: " + sessionParams.toString());
         SatelliteStats.getInstance().onSatelliteSessionMetrics(sessionParams);
@@ -277,6 +316,10 @@
         mCountOfIncomingDatagramFailed = 0;
         mIsDemoMode = false;
         mMaxNtnSignalStrengthLevel = NTN_SIGNAL_STRENGTH_NONE;
+        mCarrierId = UNKNOWN_CARRIER_ID;
+        mCountOfSatelliteNotificationDisplayed = 0;
+        mCountOfAutoExitDueToScreenOff = 0;
+        mCountOfAutoExitDueToTnNetwork = 0;
     }
 
     private static void logd(@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 7596754..9d62972 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
@@ -280,7 +280,7 @@
                     SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
                     SubscriptionInfoInternal::getSatelliteAttachEnabledForCarrier),
             new AbstractMap.SimpleImmutableEntry<>(
-                    SimInfo.COLUMN_IS_NTN,
+                    SimInfo.COLUMN_IS_ONLY_NTN,
                     SubscriptionInfoInternal::getOnlyNonTerrestrialNetwork),
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_SERVICE_CAPABILITIES,
@@ -293,7 +293,13 @@
                     SubscriptionInfoInternal::getSatelliteEntitlementStatus),
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
-                    SubscriptionInfoInternal::getSatelliteEntitlementPlmns)
+                    SubscriptionInfoInternal::getSatelliteEntitlementPlmns),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_SATELLITE_ESOS_SUPPORTED,
+                    SubscriptionInfoInternal::getSatelliteESOSSupported),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM,
+                    SubscriptionInfoInternal::getIsSatelliteProvisionedForNonIpDatagram)
     );
 
     /**
@@ -423,7 +429,7 @@
                     SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
                     SubscriptionDatabaseManager::setSatelliteAttachEnabledForCarrier),
             new AbstractMap.SimpleImmutableEntry<>(
-                    SimInfo.COLUMN_IS_NTN,
+                    SimInfo.COLUMN_IS_ONLY_NTN,
                     SubscriptionDatabaseManager::setNtn),
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_SERVICE_CAPABILITIES,
@@ -433,7 +439,13 @@
                     SubscriptionDatabaseManager::setTransferStatus),
             new AbstractMap.SimpleImmutableEntry<>(
                     SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
-                    SubscriptionDatabaseManager::setSatelliteEntitlementStatus)
+                    SubscriptionDatabaseManager::setSatelliteEntitlementStatus),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_SATELLITE_ESOS_SUPPORTED,
+                    SubscriptionDatabaseManager::setSatelliteESOSSupported),
+            new AbstractMap.SimpleImmutableEntry<>(
+                    SimInfo.COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM,
+                    SubscriptionDatabaseManager::setIsSatelliteProvisionedForNonIpDatagram)
     );
 
     /**
@@ -2063,7 +2075,7 @@
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
             return;
         }
-        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_IS_NTN, isNtn,
+        writeDatabaseAndCacheHelper(subId, SimInfo.COLUMN_IS_ONLY_NTN, isNtn,
                 SubscriptionInfoInternal.Builder::setOnlyNonTerrestrialNetwork);
     }
 
@@ -2109,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);
     }
@@ -2162,6 +2171,41 @@
     }
 
     /**
+     * Set whether the carrier roaming to satellite is using ESOS for emergency messaging.
+     *
+     * @param subId Subscription id.
+     * @param isSatelliteESOSSupported whether the carrier roaming to satellite is using ESOS for
+     * emergency messaging.
+     * @throws IllegalArgumentException if the subscription does not exist.
+     */
+    public void setSatelliteESOSSupported(int subId, int isSatelliteESOSSupported) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            return;
+        }
+        writeDatabaseAndCacheHelper(subId,
+                SimInfo.COLUMN_SATELLITE_ESOS_SUPPORTED, isSatelliteESOSSupported,
+                SubscriptionInfoInternal.Builder::setSatelliteESOSSupported);
+    }
+
+    /**
+     * Set whether the subscription is provisioned for OEM-enabled or carrier roaming NB-IOT
+     * satellite service.
+     *
+     * @param subId Subscription ID.
+     * @param isSatelliteProvisionedForNonIpDatagram {@code 1} if it is provisioned for OEM-enabled
+     * or carrier roaming NB-IOT satellite service. {@code 0} otherwise.
+     *
+     * @throws IllegalArgumentException if the subscription does not exist.
+     */
+    public void setIsSatelliteProvisionedForNonIpDatagram(int subId,
+            int isSatelliteProvisionedForNonIpDatagram) {
+        writeDatabaseAndCacheHelper(subId,
+                SimInfo.COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM,
+                isSatelliteProvisionedForNonIpDatagram,
+                SubscriptionInfoInternal.Builder::setIsSatelliteProvisionedForNonIpDatagram);
+    }
+
+    /**
      * Reload the database from content provider to the cache. This must be a synchronous operation
      * to prevent cache/database out-of-sync. Callers should be cautious to call this method because
      * it might take longer time to complete.
@@ -2399,15 +2443,22 @@
                                 SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS)))
                 .setSatelliteEntitlementPlmns(cursor.getString(
                         cursor.getColumnIndexOrThrow(
-                                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS)));
+                                SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS)))
+                .setIsSatelliteProvisionedForNonIpDatagram(cursor.getInt(
+                        cursor.getColumnIndexOrThrow(
+                                SimInfo.COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM)));
         if (mFeatureFlags.oemEnabledSatelliteFlag()) {
             builder.setOnlyNonTerrestrialNetwork(cursor.getInt(cursor.getColumnIndexOrThrow(
-                    SimInfo.COLUMN_IS_NTN)));
+                    SimInfo.COLUMN_IS_ONLY_NTN)));
         }
         if (mFeatureFlags.supportPsimToEsimConversion()) {
             builder.setTransferStatus(cursor.getInt(cursor.getColumnIndexOrThrow(
                     SimInfo.COLUMN_TRANSFER_STATUS)));
         }
+        if (mFeatureFlags.carrierRoamingNbIotNtn()) {
+            builder.setSatelliteESOSSupported(cursor.getInt(
+                    cursor.getColumnIndexOrThrow(SimInfo.COLUMN_SATELLITE_ESOS_SUPPORTED)));
+        }
         return builder.build();
     }
 
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
index c6dee7c..92e112d 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
@@ -492,6 +492,19 @@
     @NonNull private final String mSatelliteEntitlementPlmns;
 
     /**
+     * Whether the carrier roaming to satellite is using ESOS for emergency messaging.
+     * By default, its disabled. It is intended to use integer to fit the database format.
+     */
+    private final int mIsSatelliteESOSSupported;
+
+    /**
+     * Whether this subscription is provisioned for OEM-enabled or carrier roaming NB-IOT satellite
+     * service or not.
+     * By default, its disabled. It is intended to use integer to fit the database format.
+     */
+    private final int mIsSatelliteProvisionedForNonIpDatagram;
+
+    /**
      * Constructor from builder.
      *
      * @param builder Builder of {@link SubscriptionInfoInternal}.
@@ -570,6 +583,9 @@
         this.mTransferStatus = builder.mTransferStatus;
         this.mIsSatelliteEntitlementStatus = builder.mIsSatelliteEntitlementStatus;
         this.mSatelliteEntitlementPlmns = builder.mSatelliteEntitlementPlmns;
+        this.mIsSatelliteESOSSupported = builder.mIsSatelliteESOSSupported;
+        this.mIsSatelliteProvisionedForNonIpDatagram =
+                builder.mIsSatelliteProvisionedForNonIpDatagram;
     }
 
     /**
@@ -1266,6 +1282,23 @@
         return mSatelliteEntitlementPlmns;
     }
 
+    /**
+     * @return {@code 1} if the carrier roaming to satellite is using ESOS for emergency messaging.
+     */
+    public int getSatelliteESOSSupported() {
+        return mIsSatelliteESOSSupported;
+    }
+
+    /**
+     * Return whether the subscription is provisioned for oem satellite service or not.
+     *
+     * @return {@code 1} if the subscription is provisioned for oem stellite service. {@code 0}
+     * otherwise.
+     */
+    public int getIsSatelliteProvisionedForNonIpDatagram() {
+        return mIsSatelliteProvisionedForNonIpDatagram;
+    }
+
     /** @return converted {@link SubscriptionInfo}. */
     @NonNull
     public SubscriptionInfo toSubscriptionInfo() {
@@ -1305,6 +1338,7 @@
                 .setServiceCapabilities(
                         SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities))
                 .setTransferStatus(mTransferStatus)
+                .setSatelliteESOSSupported(mIsSatelliteESOSSupported == 1)
                 .build();
     }
 
@@ -1368,6 +1402,9 @@
                 + " transferStatus=" + mTransferStatus
                 + " satelliteEntitlementStatus=" + mIsSatelliteEntitlementStatus
                 + " satelliteEntitlementPlmns=" + mSatelliteEntitlementPlmns
+                + " isSatelliteESOSSupported=" + mIsSatelliteESOSSupported
+                + " isSatelliteProvisionedForNonIpDatagram="
+                + mIsSatelliteProvisionedForNonIpDatagram
                 + "]";
     }
 
@@ -1430,7 +1467,10 @@
                 && mServiceCapabilities == that.mServiceCapabilities
                 && mTransferStatus == that.mTransferStatus
                 && mIsSatelliteEntitlementStatus == that.mIsSatelliteEntitlementStatus
-                && mSatelliteEntitlementPlmns == that.mSatelliteEntitlementPlmns;
+                && mSatelliteEntitlementPlmns.equals(that.mSatelliteEntitlementPlmns)
+                && mIsSatelliteESOSSupported == that.mIsSatelliteESOSSupported
+                && mIsSatelliteProvisionedForNonIpDatagram
+                == that.mIsSatelliteProvisionedForNonIpDatagram;
     }
 
     @Override
@@ -1463,7 +1503,8 @@
                 mIsSatelliteEnabled, mCardId, mIsGroupDisabled,
                 mIsSatelliteAttachEnabledForCarrier, mIsOnlyNonTerrestrialNetwork,
                 mServiceCapabilities, mTransferStatus, mIsSatelliteEntitlementStatus,
-                mSatelliteEntitlementPlmns);
+                mSatelliteEntitlementPlmns, mIsSatelliteESOSSupported,
+                mIsSatelliteProvisionedForNonIpDatagram);
         result = 31 * result + Arrays.hashCode(mNativeAccessRules);
         result = 31 * result + Arrays.hashCode(mCarrierConfigAccessRules);
         result = 31 * result + Arrays.hashCode(mRcsConfig);
@@ -1872,6 +1913,16 @@
         private String mSatelliteEntitlementPlmns = "";
 
         /**
+         * Whether the carrier roaming to satellite is using ESOS for emergency messaging.
+         */
+        private int mIsSatelliteESOSSupported = 0;
+
+        /**
+         * Whether this subscription is provisioned for oem satellite service or not.
+         */
+        private int mIsSatelliteProvisionedForNonIpDatagram = 0;
+
+        /**
          * Default constructor.
          */
         public Builder() {
@@ -1953,6 +2004,8 @@
             mTransferStatus = info.mTransferStatus;
             mIsSatelliteEntitlementStatus = info.mIsSatelliteEntitlementStatus;
             mSatelliteEntitlementPlmns = info.mSatelliteEntitlementPlmns;
+            mIsSatelliteESOSSupported = info.mIsSatelliteESOSSupported;
+            mIsSatelliteProvisionedForNonIpDatagram = info.mIsSatelliteProvisionedForNonIpDatagram;
         }
 
         /**
@@ -2923,6 +2976,33 @@
         }
 
         /**
+         * Set whether the carrier roaming to satellite is using ESOS for emergency messaging.
+         *
+         * @param isSatelliteESOSSupported {@code 1} if the carrier roaming to satellite is using
+         * ESOS for emergency messaging.
+         * @return The builder
+         */
+        @NonNull
+        public Builder setSatelliteESOSSupported(int isSatelliteESOSSupported) {
+            mIsSatelliteESOSSupported = isSatelliteESOSSupported;
+            return this;
+        }
+
+        /**
+         * Set whether the subscription is provisioned for oem satellite service or not.
+         *
+         * @param isSatelliteProvisionedForNonIpDatagram {@code 1} if the subscription is for NTN,
+         * {@code 0} otherwise.
+         * @return The builder.
+         */
+        @NonNull
+        public Builder setIsSatelliteProvisionedForNonIpDatagram(
+                int isSatelliteProvisionedForNonIpDatagram) {
+            mIsSatelliteProvisionedForNonIpDatagram = isSatelliteProvisionedForNonIpDatagram;
+            return this;
+        }
+
+        /**
          * Build the {@link SubscriptionInfoInternal}.
          *
          * @return The {@link SubscriptionInfoInternal} instance.
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
index 2125f45..a0a050c 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;
@@ -193,9 +194,11 @@
             SimInfo.COLUMN_NR_ADVANCED_CALLING_ENABLED,
             SimInfo.COLUMN_SATELLITE_ENABLED,
             SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
-            SimInfo.COLUMN_IS_NTN,
+            SimInfo.COLUMN_IS_ONLY_NTN,
             SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS,
-            SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS
+            SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS,
+            SimInfo.COLUMN_SATELLITE_ESOS_SUPPORTED,
+            SimInfo.COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM
     );
 
     /**
@@ -456,6 +459,13 @@
          * @param subId The subscription id.
          */
         public void onUiccApplicationsEnabledChanged(int subId) {}
+
+        /**
+         * Called when {@link #getDefaultDataSubId()} changed.
+         *
+         * @param subId The subscription id.
+         */
+        public void onDefaultDataSubscriptionChanged(int subId) {}
     }
 
     /**
@@ -591,6 +601,13 @@
         // Broadcast sub Id on service initialized.
         broadcastSubId(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED,
                 getDefaultDataSubId());
+        if (mFeatureFlags.ddsCallback()) {
+            mSubscriptionManagerServiceCallbacks.forEach(
+                    callback -> callback.invokeFromExecutor(
+                            () -> callback.onDefaultDataSubscriptionChanged(
+                                    getDefaultDataSubId())));
+        }
+
         broadcastSubId(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED,
                 getDefaultVoiceSubId());
         broadcastSubId(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED,
@@ -704,7 +721,7 @@
                     return false;
                 }
             } else {
-                if (!mSubscriptionManager.canManageSubscription(subInfo.toSubscriptionInfo(),
+                if (!canManageSubscription(subInfo.toSubscriptionInfo(),
                         callingPackage)) {
                     loge("checkCarrierPrivilegeOnSubList: cannot manage sub " + subId);
                     return false;
@@ -911,6 +928,8 @@
      * @param subId The subscription id.
      */
     public void setCountryIso(int subId, @NonNull String iso) {
+        logl("setCountryIso: subId=" + subId + ", iso=" + iso);
+
         // This can throw IllegalArgumentException if the subscription does not exist.
         try {
             mSubscriptionDatabaseManager.setCountryIso(subId, iso);
@@ -927,6 +946,8 @@
      * @param carrierName The carrier name.
      */
     public void setCarrierName(int subId, @NonNull String carrierName) {
+        logl("setCarrierName: subId=" + subId + ", carrierName=" + carrierName);
+
         // This can throw IllegalArgumentException if the subscription does not exist.
         try {
             mSubscriptionDatabaseManager.setCarrierName(subId, carrierName);
@@ -998,6 +1019,9 @@
      * @param numberFromIms The phone number retrieved from IMS.
      */
     public void setNumberFromIms(int subId, @NonNull String numberFromIms) {
+        logl("setNumberFromIms: subId=" + subId + ", number="
+                + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, numberFromIms));
+
         // This can throw IllegalArgumentException if the subscription does not exist.
         try {
             mSubscriptionDatabaseManager.setNumberFromIms(subId, numberFromIms);
@@ -1427,8 +1451,8 @@
             SatelliteController satelliteController = SatelliteController.getInstance();
             boolean isSatelliteEnabledOrBeingEnabled = false;
             if (satelliteController != null) {
-                isSatelliteEnabledOrBeingEnabled = satelliteController.isSatelliteEnabled()
-                        || satelliteController.isSatelliteBeingEnabled();
+                isSatelliteEnabledOrBeingEnabled =
+                        satelliteController.isSatelliteEnabledOrBeingEnabled();
             }
 
             if (!isSatelliteEnabledOrBeingEnabled) {
@@ -1540,6 +1564,9 @@
                             MccTable.updateMccMncConfiguration(mContext, mccMnc);
                         }
                         setMccMnc(subId, mccMnc);
+                        if (isSatelliteSpn(subInfo.getDisplayName()) || isSatellitePlmn(mccMnc)) {
+                            setNtn(subId, true);
+                        }
                     } else {
                         loge("updateSubscription: mcc/mnc is empty");
                     }
@@ -1812,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);
         }
     }
 
@@ -1889,7 +1914,10 @@
                     + "carrier privilege");
         }
 
-        enforceTelephonyFeatureWithException(callingPackage, "getAllSubInfoList");
+        if (!mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_force_phone_globals_creation)) {
+            enforceTelephonyFeatureWithException(callingPackage, "getAllSubInfoList");
+        }
 
         return getSubscriptionInfoStreamAsUser(BINDER_WRAPPER.getCallingUserHandle())
                 // callers have READ_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE can get a full
@@ -1933,7 +1961,10 @@
                     + "carrier privilege");
         }
 
-        enforceTelephonyFeatureWithException(callingPackage, "getActiveSubscriptionInfo");
+        if (!mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_force_phone_globals_creation)) {
+            enforceTelephonyFeatureWithException(callingPackage, "getActiveSubscriptionInfo");
+        }
 
         SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
                 .getSubscriptionInfoInternal(subId);
@@ -2062,7 +2093,10 @@
             return Collections.emptyList();
         }
 
-        enforceTelephonyFeatureWithException(callingPackage, "getActiveSubscriptionInfoList");
+        if (!mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_force_phone_globals_creation)) {
+            enforceTelephonyFeatureWithException(callingPackage, "getActiveSubscriptionInfoList");
+        }
 
         if (isForAllProfiles) {
             enforcePermissionAccessAllUserProfiles();
@@ -2154,7 +2188,11 @@
         enforcePermissions("getAvailableSubscriptionInfoList",
                 Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
 
-        enforceTelephonyFeatureWithException(callingPackage, "getAvailableSubscriptionInfoList");
+        if (!mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_force_phone_globals_creation)) {
+            enforceTelephonyFeatureWithException(callingPackage,
+                    "getAvailableSubscriptionInfoList");
+        }
 
         return getAvailableSubscriptionsInternalStream()
                 .sorted(Comparator.comparing(SubscriptionInfoInternal::getSimSlotIndex)
@@ -2229,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());
@@ -2738,7 +2776,10 @@
             return Collections.emptyList();
         }
 
-        enforceTelephonyFeatureWithException(callingPackage, "getOpportunisticSubscriptions");
+        if (!mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_force_phone_globals_creation)) {
+            enforceTelephonyFeatureWithException(callingPackage, "getOpportunisticSubscriptions");
+        }
 
         return mSubscriptionDatabaseManager.getAllSubscriptions().stream()
                 // callers have READ_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE can get a full
@@ -2952,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")))
@@ -3149,6 +3190,11 @@
 
                 broadcastSubId(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED,
                         subId);
+                if (mFeatureFlags.ddsCallback()) {
+                    mSubscriptionManagerServiceCallbacks.forEach(
+                            callback -> callback.invokeFromExecutor(
+                                    () -> callback.onDefaultDataSubscriptionChanged(subId)));
+                }
 
                 updateDefaultSubId();
             }
@@ -3311,7 +3357,10 @@
     public int[] getActiveSubIdList(boolean visibleOnly) {
         enforcePermissions("getActiveSubIdList", Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
 
-        enforceTelephonyFeatureWithException(getCurrentPackageName(), "getActiveSubIdList");
+        if (!mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_force_phone_globals_creation)) {
+            enforceTelephonyFeatureWithException(getCurrentPackageName(), "getActiveSubIdList");
+        }
 
         // UserHandle.ALL because this API is exposed as system API.
         return getActiveSubIdListAsUser(visibleOnly, UserHandle.ALL);
@@ -3875,10 +3924,20 @@
         switch(source) {
             case SubscriptionManager.PHONE_NUMBER_SOURCE_UICC:
                 final Phone phone = PhoneFactory.getPhone(getSlotIndex(subId));
-                if (phone != null) {
-                    return TextUtils.emptyIfNull(phone.getLine1Number());
-                } else {
+                if (mFeatureFlags.uiccPhoneNumberFix()) {
+                    if (phone != null) {
+                        String number = phone.getLine1Number();
+                        if (!TextUtils.isEmpty(number)) {
+                            return number;
+                        }
+                    }
                     return subInfo.getNumber();
+                } else {
+                    if (phone != null) {
+                        return TextUtils.emptyIfNull(phone.getLine1Number());
+                    } else {
+                        return subInfo.getNumber();
+                    }
                 }
             case SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER:
                 return subInfo.getNumberFromCarrier();
@@ -3986,6 +4045,9 @@
     @RequiresPermission("carrier privileges")
     public void setPhoneNumber(int subId, @PhoneNumberSource int source, @NonNull String number,
             @NonNull String callingPackage, @Nullable String callingFeatureId) {
+        logl("setPhoneNumber: subId=" + subId + ", number="
+                + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, number)
+                + ", calling package=" + callingPackage);
         if (!TelephonyPermissions.checkCarrierPrivilegeForSubId(mContext, subId)) {
             throw new SecurityException("setPhoneNumber for CARRIER needs carrier privilege.");
         }
@@ -4494,10 +4556,14 @@
      */
     @NonNull
     private String getCallingPackage() {
-        if (Binder.getCallingUid() == Process.PHONE_UID) {
+        if (UserHandle.isSameApp(Binder.getCallingUid(), Process.PHONE_UID)) {
             // Too many packages running with phone uid. Just return one here.
             return "com.android.phone";
         }
+        if (mFeatureFlags.hsumPackageManager()) {
+            return Arrays.toString(mContext.createContextAsUser(Binder.getCallingUserHandle(), 0)
+                    .getPackageManager().getPackagesForUid(Binder.getCallingUid()));
+        }
         return Arrays.toString(mContext.getPackageManager().getPackagesForUid(
                 Binder.getCallingUid()));
     }
@@ -4591,6 +4657,75 @@
     }
 
     /**
+     * Set the satellite ESOS supported value in the subscription database.
+     *
+     * @param subId subscription id.
+     * @param isSatelliteESOSSupported {@code true} satellite ESOS supported true.
+     */
+    public void setSatelliteESOSSupported(int subId, @NonNull boolean isSatelliteESOSSupported) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            return;
+        }
+        try {
+            mSubscriptionDatabaseManager.setSatelliteESOSSupported(subId,
+                    isSatelliteESOSSupported ? 1 : 0);
+        } catch (IllegalArgumentException e) {
+            loge("setSatelliteESOSSupported: invalid subId=" + subId);
+        }
+    }
+
+    /**
+     * Set whether the subscription is provisioned for OEM-enabled or carrier roaming NB-IOT
+     * satellite service or not.
+     *
+     * @param subId subscription id.
+     * @param isSatelliteProvisionedForNonIpDatagram {@code true} if subId is provisioned.
+     * {@code false} otherwise.
+     */
+    public void setIsSatelliteProvisionedForNonIpDatagram(int subId,
+            boolean isSatelliteProvisionedForNonIpDatagram) {
+        try {
+            mSubscriptionDatabaseManager.setIsSatelliteProvisionedForNonIpDatagram(
+                    subId, (isSatelliteProvisionedForNonIpDatagram ? 1 : 0));
+        } catch (IllegalArgumentException e) {
+            loge("setIsSatelliteProvisionedForNonIpDatagram: invalid subId=" + subId);
+        }
+    }
+
+    /**
+     * Get the satellite ESOS supported value in the subscription database.
+     *
+     * @param subId subscription id.
+     * @return the satellite ESOS supported true or false.
+     */
+    public boolean getSatelliteESOSSupported(int subId) {
+        if (!mFeatureFlags.carrierRoamingNbIotNtn()) {
+            return false;
+        }
+        SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager.getSubscriptionInfoInternal(
+                subId);
+        if (subInfo == null) {
+            return false;
+        }
+
+        return subInfo.getSatelliteESOSSupported() == 1;
+    }
+
+    /**
+     * Get whether the subscription is provisioned for OEM-enabled or carrier roaming NB-IOT
+     * satellite service or not.
+     *
+     * @param subId subscription id.
+     * @return {@code true} if it is provisioned for oem satellite service. {@code false} otherwise.
+     */
+    public boolean isSatelliteProvisionedForNonIpDatagram(int subId) {
+        SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager.getSubscriptionInfoInternal(
+                subId);
+
+        return subInfo.getIsSatelliteProvisionedForNonIpDatagram() == 1;
+    }
+
+    /**
      * checks whether esim bootstrap is activated for any of the available active subscription info
      * list.
      *
@@ -4633,9 +4768,16 @@
      */
     @Nullable
     private String getCurrentPackageName() {
+        if (mFeatureFlags.hsumPackageManager()) {
+            PackageManager pm = mContext.createContextAsUser(Binder.getCallingUserHandle(), 0)
+                    .getPackageManager();
+            if (pm == null) return null;
+            String[] callingPackageNames = pm.getPackagesForUid(Binder.getCallingUid());
+            return (callingPackageNames == null) ? null : callingPackageNames[0];
+        }
         if (mPackageManager == null) return null;
-        String[] callingUids = mPackageManager.getPackagesForUid(Binder.getCallingUid());
-        return (callingUids == null) ? null : callingUids[0];
+        String[] callingPackageNames = mPackageManager.getPackagesForUid(Binder.getCallingUid());
+        return (callingPackageNames == null) ? null : callingPackageNames[0];
     }
 
     /**
@@ -4760,6 +4902,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/IccFileHandler.java b/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
index 5f8ccb4..a3f34c4 100644
--- a/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
+++ b/src/java/com/android/internal/telephony/uicc/IccFileHandler.java
@@ -16,14 +16,18 @@
 
 package com.android.internal.telephony.uicc;
 
+import static com.android.internal.telephony.util.TelephonyUtils.FORCE_VERBOSE_STATE_LOGGING;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.AsyncResult;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
+import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
 
@@ -31,7 +35,9 @@
  * {@hide}
  */
 public abstract class IccFileHandler extends Handler implements IccConstants {
-    private static final boolean VDBG = false;
+    protected static final String LOG_TAG = "IccFileHandler";
+    private static final boolean VDBG = FORCE_VERBOSE_STATE_LOGGING ||
+            Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
 
     //from TS 11.11 9.1 or elsewhere
     static protected final int COMMAND_READ_BINARY = 0xb0;
diff --git a/src/java/com/android/internal/telephony/uicc/IccRecords.java b/src/java/com/android/internal/telephony/uicc/IccRecords.java
index ae93b09..cccbf19 100644
--- a/src/java/com/android/internal/telephony/uicc/IccRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IccRecords.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.uicc;
 
+import static com.android.internal.telephony.util.TelephonyUtils.FORCE_VERBOSE_STATE_LOGGING;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -34,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;
@@ -59,7 +62,6 @@
 public abstract class IccRecords extends Handler implements IccConstants {
     private static final String LOG_TAG = "IccRecords";
     protected static final boolean DBG = true;
-    private static final boolean FORCE_VERBOSE_STATE_LOGGING = false; /* stopship if true */
     protected static final boolean VDBG =  FORCE_VERBOSE_STATE_LOGGING ||
             Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
 
@@ -1299,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/InstallCarrierAppTrampolineActivity.java b/src/java/com/android/internal/telephony/uicc/InstallCarrierAppTrampolineActivity.java
index 2c29266..a07c2ec 100644
--- a/src/java/com/android/internal/telephony/uicc/InstallCarrierAppTrampolineActivity.java
+++ b/src/java/com/android/internal/telephony/uicc/InstallCarrierAppTrampolineActivity.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
@@ -107,7 +108,8 @@
         super.onActivityResult(requestCode, resultCode, data);
         if (requestCode == INSTALL_CARRIER_APP_DIALOG_REQUEST) {
             if (resultCode == DOWNLOAD_RESULT) {
-                startActivity(InstallCarrierAppUtils.getPlayStoreIntent(mPackageName));
+                startActivityAsUser(InstallCarrierAppUtils.getPlayStoreIntent(mPackageName),
+                        UserHandle.CURRENT);
             }
             finishNoAnimation();
         }
diff --git a/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java b/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java
index 9591a498..b705cbc 100644
--- a/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/IsimUiccRecords.java
@@ -16,17 +16,23 @@
 
 package com.android.internal.telephony.uicc;
 
+import android.annotation.NonNull;
+
+import static com.android.internal.telephony.util.TelephonyUtils.FORCE_VERBOSE_STATE_LOGGING;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
 import android.os.AsyncResult;
 import android.os.Build;
 import android.os.Message;
+import android.os.UserHandle;
 import android.telephony.SubscriptionManager;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.gsm.SimTlv;
 import com.android.telephony.Rlog;
 
@@ -43,7 +49,6 @@
     protected static final String LOG_TAG = "IsimUiccRecords";
 
     private static final boolean DBG = true;
-    private static final boolean FORCE_VERBOSE_STATE_LOGGING = false; /* stopship if true */
     private static final boolean VDBG =  FORCE_VERBOSE_STATE_LOGGING ||
             Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
     private static final boolean DUMP_RECORDS = false;  // Note: PII is logged when this is true
@@ -64,6 +69,9 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private String auth_rsp;
 
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
+
     private static final int TAG_ISIM_VALUE = 0x80;     // From 3GPP TS 31.103
 
     @Override
@@ -78,9 +86,10 @@
                 + " mSmss TPMR=" + getSmssTpmrValue()) : "");
     }
 
-    public IsimUiccRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
+    public IsimUiccRecords(@NonNull UiccCardApplication app, @NonNull Context c,
+            @NonNull CommandsInterface ci, @NonNull FeatureFlags flags) {
         super(app, c, ci);
-
+        mFeatureFlags = flags;
         mRecordsRequested = false;  // No load request is made till SIM ready
         //todo: currently locked state for ISIM is not handled well and may cause app state to not
         //be broadcast
@@ -387,7 +396,11 @@
         Intent intent = new Intent(INTENT_ISIM_REFRESH);
         log("send ISim REFRESH: " + INTENT_ISIM_REFRESH);
         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mParentApp.getPhoneId());
-        mContext.sendBroadcast(intent);
+        if (mFeatureFlags.hsumBroadcast()) {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        } else {
+            mContext.sendBroadcast(intent);
+        }
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/uicc/PinStorage.java b/src/java/com/android/internal/telephony/uicc/PinStorage.java
index e4a0cfa..e26050e 100644
--- a/src/java/com/android/internal/telephony/uicc/PinStorage.java
+++ b/src/java/com/android/internal/telephony/uicc/PinStorage.java
@@ -36,6 +36,7 @@
 import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_SUCCESS;
 import static com.android.internal.telephony.uicc.IccCardStatus.PinState.PINSTATE_ENABLED_NOT_VERIFIED;
 import static com.android.internal.telephony.uicc.IccCardStatus.PinState.PINSTATE_ENABLED_VERIFIED;
+import static com.android.internal.telephony.util.TelephonyUtils.FORCE_VERBOSE_STATE_LOGGING;
 
 import android.annotation.Nullable;
 import android.app.KeyguardManager;
@@ -56,6 +57,7 @@
 import android.telephony.TelephonyManager.SimState;
 import android.util.Base64;
 import android.util.IndentingPrintWriter;
+import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.R;
@@ -88,7 +90,8 @@
  */
 public class PinStorage extends Handler {
     private static final String TAG = "PinStorage";
-    private static final boolean VDBG = false;  // STOPSHIP if true
+    private static final boolean VDBG = FORCE_VERBOSE_STATE_LOGGING ||
+            Rlog.isLoggable(TAG, Log.VERBOSE);
 
     /**
      * Time duration in milliseconds to allow automatic PIN verification after reboot. All unused
diff --git a/src/java/com/android/internal/telephony/uicc/SIMRecords.java b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
index a97b00b..4b0e63c 100644
--- a/src/java/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
@@ -19,6 +19,8 @@
 import static android.telephony.SmsManager.STATUS_ON_ICC_READ;
 import static android.telephony.SmsManager.STATUS_ON_ICC_UNREAD;
 
+import static com.android.internal.telephony.util.TelephonyUtils.FORCE_VERBOSE_STATE_LOGGING;
+
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
@@ -48,6 +50,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * {@hide}
@@ -56,8 +59,7 @@
     protected static final String LOG_TAG = "SIMRecords";
 
     private static final boolean CRASH_RIL = false;
-    private static final boolean FORCE_VERBOSE_STATE_LOGGING = false; /* stopship if true */
-    private static final boolean VDBG =  FORCE_VERBOSE_STATE_LOGGING ||
+    private static final boolean VDBG = FORCE_VERBOSE_STATE_LOGGING ||
             Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
 
     // ***** Instance Variables
@@ -1278,15 +1280,22 @@
 
                 case EVENT_GET_FPLMN_SIZE_DONE:
                     ar = (AsyncResult) msg.obj;
+                    int key = msg.arg2;
+
+                    Message response;
+                    Pair<Message, Object> transaction = null;
+                    if (ar.exception != null && ar.userObj != null) {
+                        response = (Message) ar.userObj;
+                    } else {
+                        transaction = retrievePendingTransaction(key);
+                        response = Objects.requireNonNull(transaction.first);
+                    }
+
                     if (ar.exception != null) {
-                        Message response = (Message) ar.userObj;
                         AsyncResult.forMessage(response).exception = ar.exception;
                         response.sendToTarget();
                         break;
                     }
-                    int key = msg.arg2;
-                    Pair<Message, Object> transaction = retrievePendingTransaction(key);
-                    Message response = transaction.first;
                     List<String> fplmns = (List<String>) transaction.second;
                     int dataLength = (int) ar.result;
                     if (dataLength < 0 || dataLength % FPLMN_BYTE_SIZE != 0) {
diff --git a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
index fe19e99..3bda29f 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccCardApplication.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.uicc;
 
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.AsyncResult;
@@ -29,6 +30,7 @@
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.PersoSubState;
@@ -105,11 +107,14 @@
     private RegistrantList mPinLockedRegistrants = new RegistrantList();
     private RegistrantList mNetworkLockedRegistrants = new RegistrantList();
 
-    public UiccCardApplication(UiccProfile uiccProfile,
-                        IccCardApplicationStatus as,
-                        Context c,
-                        CommandsInterface ci) {
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
+
+    public UiccCardApplication(@NonNull UiccProfile uiccProfile,
+            @NonNull IccCardApplicationStatus as, @NonNull Context c, @NonNull CommandsInterface ci,
+            @NonNull FeatureFlags flags) {
         if (DBG) log("Creating UiccApp: " + as);
+        mFeatureFlags = flags;
         mUiccProfile = uiccProfile;
         mAppState = as.app_state;
         mAppType = as.app_type;
@@ -208,7 +213,7 @@
         } else if (type == AppType.APPTYPE_RUIM || type == AppType.APPTYPE_CSIM){
             return new RuimRecords(this, c, ci);
         } else if (type == AppType.APPTYPE_ISIM) {
-            return new IsimUiccRecords(this, c, ci);
+            return new IsimUiccRecords(this, c, ci, mFeatureFlags);
         } else {
             // Unknown app type (maybe detection is still in progress)
             return null;
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
index 84e84d9..dd71c44 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccController.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -19,6 +19,8 @@
 import static android.telephony.TelephonyManager.UNINITIALIZED_CARD_ID;
 import static android.telephony.TelephonyManager.UNSUPPORTED_CARD_ID;
 
+import static com.android.internal.telephony.util.TelephonyUtils.FORCE_VERBOSE_STATE_LOGGING;
+
 import static java.util.Arrays.copyOf;
 
 import android.Manifest;
@@ -35,6 +37,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.RegistrantList;
+import android.os.UserHandle;
 import android.preference.PreferenceManager;
 import android.sysprop.TelephonyProperties;
 import android.telephony.AnomalyReporter;
@@ -64,6 +67,7 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.RadioConfig;
 import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.euicc.EuiccCard;
@@ -129,9 +133,10 @@
  * See also {@link com.android.internal.telephony.IccCard}
  */
 public class UiccController extends Handler {
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false; //STOPSHIP if true
     private static final String LOG_TAG = "UiccController";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = FORCE_VERBOSE_STATE_LOGGING ||
+            Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
 
     public static final int INVALID_SLOT_ID = -1;
 
@@ -245,22 +250,26 @@
     // LocalLog buffer to hold important SIM related events for debugging
     private static LocalLog sLocalLog = new LocalLog(TelephonyUtils.IS_DEBUGGABLE ? 256 : 64);
 
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
+
     /**
      * API to make UiccController singleton if not already created.
      */
-    public static UiccController make(Context c) {
+    public static UiccController make(@NonNull Context c, @NonNull FeatureFlags flags) {
         synchronized (mLock) {
             if (mInstance != null) {
                 throw new RuntimeException("UiccController.make() should only be called once");
             }
-            mInstance = new UiccController(c);
+            mInstance = new UiccController(c, flags);
             return mInstance;
         }
     }
 
-    private UiccController(Context c) {
+    private UiccController(@NonNull Context c, @NonNull FeatureFlags flags) {
         if (DBG) log("Creating UiccController");
         mContext = c;
+        mFeatureFlags = flags;
         mCis = PhoneFactory.getCommandsInterfaces();
         int numPhysicalSlots = c.getResources().getInteger(
                 com.android.internal.R.integer.config_num_physical_slots);
@@ -292,7 +301,7 @@
             mCis[i].registerForIccRefresh(this, EVENT_SIM_REFRESH, i);
         }
 
-        mLauncher = new UiccStateChangedLauncher(c, this);
+        mLauncher = new UiccStateChangedLauncher(c, this, mFeatureFlags);
         mCardStrings = loadCardStrings();
         mDefaultEuiccCardId = UNINITIALIZED_CARD_ID;
 
@@ -780,9 +789,10 @@
         intent.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone");
         intent.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, state);
         intent.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason);
-        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId);
+        int subId = SubscriptionManager.getSubscriptionId(phoneId);
+        SubscriptionManager.putPhoneIdAndMaybeSubIdExtra(intent, phoneId, subId);
         Rlog.d(LOG_TAG, "Broadcasting intent ACTION_SIM_STATE_CHANGED " + state + " reason "
-                + reason + " for phone: " + phoneId);
+                + reason + " for phone: " + phoneId + " sub: " + subId);
         IntentBroadcaster.getInstance().broadcastStickyIntent(mContext, intent, phoneId);
     }
 
@@ -798,7 +808,8 @@
             Intent intent = new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             intent.putExtra(TelephonyManager.EXTRA_SIM_STATE, state);
-            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId);
+            int subId = SubscriptionManager.getSubscriptionId(phoneId);
+            SubscriptionManager.putPhoneIdAndMaybeSubIdExtra(intent, phoneId, subId);
             // TODO(b/130664115) we manually populate this intent with the slotId. In the future we
             // should do a review of whether to make this public
             UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(phoneId);
@@ -811,8 +822,13 @@
             }
             Rlog.d(LOG_TAG, "Broadcasting intent ACTION_SIM_CARD_STATE_CHANGED "
                     + TelephonyManager.simStateToString(state) + " for phone: " + phoneId
-                    + " slot: " + slotId + " port: " + portIndex);
-            mContext.sendBroadcast(intent, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+                    + " slot: " + slotId + " port: " + portIndex + " sub: " + subId);
+            if (mFeatureFlags.hsumBroadcast()) {
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                        Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+            } else {
+                mContext.sendBroadcast(intent, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+            }
             TelephonyMetrics.getInstance().updateSimState(phoneId, state);
         }
     }
@@ -838,7 +854,8 @@
             Intent intent = new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             intent.putExtra(TelephonyManager.EXTRA_SIM_STATE, state);
-            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId);
+            int subId = SubscriptionManager.getSubscriptionId(phoneId);
+            SubscriptionManager.putPhoneIdAndMaybeSubIdExtra(intent, phoneId, subId);
             // TODO(b/130664115) we populate this intent with the actual slotId. In the future we
             // should do a review of whether to make this public
             UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(phoneId);
@@ -849,9 +866,14 @@
             }
             Rlog.d(LOG_TAG, "Broadcasting intent ACTION_SIM_APPLICATION_STATE_CHANGED "
                     + TelephonyManager.simStateToString(state)
-                    + " for phone: " + phoneId + " slot: " + slotId + "port: "
-                    + slot.getPortIndexFromPhoneId(phoneId));
-            mContext.sendBroadcast(intent, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+                    + " for phone: " + phoneId + " slot: " + slotId + " port: "
+                    + slot.getPortIndexFromPhoneId(phoneId) + " sub: " + subId);
+            if (mFeatureFlags.hsumBroadcast()) {
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                        Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+            } else {
+                mContext.sendBroadcast(intent, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+            }
             TelephonyMetrics.getInstance().updateSimState(phoneId, state);
         }
     }
@@ -1453,8 +1475,13 @@
         options.setBackgroundActivityStartsAllowed(true);
         Intent intent = new Intent(TelephonyManager.ACTION_SIM_SLOT_STATUS_CHANGED);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mContext.sendBroadcast(intent, android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
-                options.toBundle());
+        if (mFeatureFlags.hsumBroadcast()) {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                    android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, options.toBundle());
+        } else {
+            mContext.sendBroadcast(intent, android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+                    options.toBundle());
+        }
     }
 
     private boolean hasActivePort(IccSimPortInfo[] simPortInfos) {
diff --git a/src/java/com/android/internal/telephony/uicc/UiccPort.java b/src/java/com/android/internal/telephony/uicc/UiccPort.java
index 9e341ef..905db70 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccPort.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccPort.java
@@ -31,7 +31,6 @@
 import com.android.internal.telephony.TelephonyComponentFactory;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.flags.FeatureFlagsImpl;
-import com.android.internal.telephony.flags.Flags;
 import com.android.telephony.Rlog;
 
 import java.io.FileDescriptor;
@@ -442,15 +441,13 @@
      * channel that may have been assigned to other client.
      */
     private void cleanupOpenLogicalChannelRecordsIfNeeded() {
-        if (Flags.cleanupOpenLogicalChannelRecordOnDispose()) {
-            synchronized (mOpenChannelRecords) {
-                for (OpenLogicalChannelRecord record : mOpenChannelRecords) {
-                    if (DBG) log("Clean up " + record);
-                    record.mRequest.binder.unlinkToDeath(record, /*flags=*/ 0);
-                    record.mRequest.binder = null;
-                }
-                mOpenChannelRecords.clear();
+        synchronized (mOpenChannelRecords) {
+            for (OpenLogicalChannelRecord record : mOpenChannelRecords) {
+                if (DBG) log("Clean up " + record);
+                record.mRequest.binder.unlinkToDeath(record, /*flags=*/ 0);
+                record.mRequest.binder = null;
             }
+            mOpenChannelRecords.clear();
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
index a22f075..2551cb8 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
@@ -19,6 +19,7 @@
 import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT;
 import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_FAILURE;
 import static com.android.internal.telephony.TelephonyStatsLog.PIN_STORAGE_EVENT__EVENT__PIN_VERIFICATION_SUCCESS;
+import static com.android.internal.telephony.util.TelephonyUtils.FORCE_VERBOSE_STATE_LOGGING;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -40,6 +41,7 @@
 import android.os.PersistableBundle;
 import android.os.Registrant;
 import android.os.RegistrantList;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
@@ -53,6 +55,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CarrierAppUtils;
@@ -67,6 +70,7 @@
 import com.android.internal.telephony.TelephonyStatsLog;
 import com.android.internal.telephony.cat.CatService;
 import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
@@ -101,11 +105,13 @@
 public class UiccProfile extends IccCard {
     protected static final String LOG_TAG = "UiccProfile";
     protected static final boolean DBG = true;
-    private static final boolean VDBG = false; //STOPSHIP if true
+    private static final boolean VDBG = FORCE_VERBOSE_STATE_LOGGING ||
+            Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
 
     private static final String OPERATOR_BRAND_OVERRIDE_PREFIX = "operator_branding_";
 
-    private final @NonNull FeatureFlags mFlags;
+    @NonNull
+    private final FeatureFlags mFlags;
     // The lock object is created by UiccSlot that owns the UiccCard that owns this UiccProfile.
     // This is to share the lock between UiccSlot, UiccCard and UiccProfile for now.
     private final Object mLock;
@@ -1139,7 +1145,7 @@
                     //Create newly added Applications
                     if (i < ics.mApplications.length) {
                         mUiccApplications[i] = new UiccCardApplication(this,
-                                ics.mApplications[i], mContext, mCi);
+                                ics.mApplications[i], mContext, mCi, mFlags);
                     }
                 } else if (i >= ics.mApplications.length) {
                     //Delete removed applications
@@ -1155,7 +1161,9 @@
 
             // Reload the carrier privilege rules if necessary.
             log("Before privilege rules: " + mCarrierPrivilegeRules + " : " + ics.mCardState);
-            if (mCarrierPrivilegeRules == null && ics.mCardState == CardState.CARDSTATE_PRESENT) {
+            if (mCarrierPrivilegeRules == null && ics.mCardState == CardState.CARDSTATE_PRESENT && (
+                    !Flags.uiccAppCountCheckToCreateChannel()
+                            || mLastReportedNumOfUiccApplications > 0)) {
                 mCarrierPrivilegeRules = new UiccCarrierPrivilegeRules(this,
                         mHandler.obtainMessage(EVENT_CARRIER_PRIVILEGES_LOADED));
             } else if (mCarrierPrivilegeRules != null
@@ -1349,7 +1357,7 @@
     private void promptInstallCarrierApp(String pkgName) {
         Intent showDialogIntent = InstallCarrierAppTrampolineActivity.get(mContext, pkgName);
         showDialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(showDialogIntent);
+        mContext.startActivityAsUser(showDialogIntent, UserHandle.CURRENT);
     }
 
     private void onCarrierPrivilegesLoadedMessage() {
diff --git a/src/java/com/android/internal/telephony/uicc/UiccStateChangedLauncher.java b/src/java/com/android/internal/telephony/uicc/UiccStateChangedLauncher.java
index 3ef421a..ff48fd1 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccStateChangedLauncher.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccStateChangedLauncher.java
@@ -16,15 +16,18 @@
 
 package com.android.internal.telephony.uicc;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.Message;
+import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import com.android.internal.R;
 import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
 
 /**
@@ -46,7 +49,12 @@
     private UiccController mUiccController;
     private boolean[] mIsRestricted = null;
 
-    public UiccStateChangedLauncher(Context context, UiccController controller) {
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
+
+    public UiccStateChangedLauncher(Context context, UiccController controller,
+            @NonNull FeatureFlags flags) {
+        mFeatureFlags = flags;
         sDeviceProvisioningPackage = context.getResources().getString(
                 R.string.config_deviceProvisioningPackage);
         if (sDeviceProvisioningPackage != null && !sDeviceProvisioningPackage.isEmpty()) {
@@ -92,7 +100,11 @@
         Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
         intent.setPackage(sDeviceProvisioningPackage);
         try {
-            mContext.sendBroadcast(intent);
+            if (mFeatureFlags.hsumBroadcast()) {
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            } else {
+                mContext.sendBroadcast(intent);
+            }
         } catch (Exception e) {
             Log.e(TAG, e.toString());
         }
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 f42d5a2..1d9dc68 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
@@ -18,14 +18,16 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.SharedPreferences;
+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;
@@ -34,14 +36,20 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.List;
-import java.util.NoSuchElementException;
 
 /**
  * This class sends a list of APDU commands to an AID on a UICC. A logical channel will be opened
  * 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
  */
@@ -55,10 +63,11 @@
     // 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";
-    private static final String ISD_R_AID = "A0000005591010FFFFFFFF8900000100";
+    static final String ISD_R_AID = "A0000005591010FFFFFFFF8900000100";
     private static final String CHANNEL_RESPONSE_ID_PRE = "esim-res-id";
 
     private static void logv(String msg) {
@@ -69,6 +78,10 @@
         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;
@@ -77,10 +90,16 @@
     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;
 
     /**
@@ -88,6 +107,9 @@
      */
     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;
@@ -96,7 +118,8 @@
         mTransmitApdu = new TransmitApduLogicalChannelInvocation(ci);
         mChannelKey = CHANNEL_ID_PRE + "_" + phoneId;
         mChannelResponseKey = CHANNEL_RESPONSE_ID_PRE + "_" + phoneId;
-        closeExistingChannelIfExists();
+        mHandler = new Handler();
+        mChannelInUse = false;
     }
 
     /**
@@ -115,86 +138,125 @@
             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();
-                byte[] selectResponse = openChannelResponse.getSelectResponse();
-                if (mAid.equals(ISD_R_AID)
-                      && 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) {
-                    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;
-                if (mAid.equals(ISD_R_AID)) {
-                   PreferenceManager.getDefaultSharedPreferences(mContext)
-                         .edit().putInt(mChannelKey, channel).apply();
-                   PreferenceManager.getDefaultSharedPreferences(mContext)
-                        .edit().putString(mChannelResponseKey,
-                           Base64.encodeToString(selectResponse, Base64.DEFAULT)).apply();
-                }
-                try {
-                    requestProvider.buildRequest(selectResponse, 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);
+        }
+    }
+
+    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;
+                            resultCallback.onException(
+                                    new ApduException("Failed to open logical channel for AID: "
+                                            + mAid + ", with status: " + status));
+                            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);
     }
 
     /**
@@ -207,6 +269,7 @@
     private void sendCommand(
             List<ApduCommand> commands,
             int index,
+            boolean closeChannelImmediately,
             ApduSenderResultCallback resultCallback,
             Handler handler) {
         ApduCommand command = commands.get(index);
@@ -221,9 +284,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;
                                 }
 
@@ -233,11 +308,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);
@@ -286,6 +367,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.
      *
@@ -303,16 +419,9 @@
         mCloseChannel.invoke(channel, new AsyncResultCallback<Boolean>() {
             @Override
             public void onResult(Boolean aBoolean) {
-                synchronized (mChannelLock) {
-                    if (mAid.equals(ISD_R_AID)) {
-                      PreferenceManager.getDefaultSharedPreferences(mContext)
-                             .edit().remove(mChannelKey).apply();
-                      PreferenceManager.getDefaultSharedPreferences(mContext)
-                             .edit().remove(mChannelResponseKey).apply();
-                    }
-                    mChannelOpened = false;
-                    mChannelLock.notify();
-                }
+                tearDownPreferences();
+                mChannelOpened = false;
+                releaseChannelLock();
 
                 if (exception == null) {
                     resultCallback.onResult(response);
@@ -324,37 +433,97 @@
     }
 
     /**
-     * Cleanup the existing opened channel which was remainined opened earlier due
-     * to failure or crash.
+     * 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.
      */
-    private void closeExistingChannelIfExists() {
-        if (mCloseChannel != null) {
-            int channelId = PreferenceManager.getDefaultSharedPreferences(mContext)
+    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) {
-                logv("Trying to clean up the opened channel : " +  channelId);
-                synchronized (mChannelLock) {
-                    mChannelOpened = true;
-                    mChannelLock.notify();
+        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();
                 }
-                mCloseChannel.invoke(channelId, new AsyncResultCallback<Boolean>() {
-                    @Override
-                    public void onResult(Boolean isSuccess) {
-                        if (isSuccess) {
-                          logv("Channel closed successfully: " +  channelId);
-                          PreferenceManager.getDefaultSharedPreferences(mContext)
-                                 .edit().remove(mChannelResponseKey).apply();
-                          PreferenceManager.getDefaultSharedPreferences(mContext)
-                                 .edit().remove(mChannelKey).apply();
-                       }
-
-                       synchronized (mChannelLock) {
-                           mChannelOpened = false;
-                           mChannelLock.notify();
-                      }
-                    }
-                }, new Handler());
+                // 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/com/android/internal/telephony/CarrierAppUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
index e06e4fe..cb37d88 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
@@ -23,7 +23,6 @@
 import android.os.Bundle;
 import android.os.CarrierAssociatedAppEntry;
 import android.os.UserHandle;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.test.mock.MockContentProvider;
@@ -35,12 +34,9 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.telephony.flags.Flags;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
@@ -63,9 +59,6 @@
     private static final int USER_ID = 12345;
     private static final String CALLING_PACKAGE = "phone";
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
     // Mocked classes
     private Context mContext;
     private PackageManager mPackageManager;
@@ -86,7 +79,6 @@
 
     @Before
     public void setUp() throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_HIDE_PREINSTALLED_CARRIER_APP_AT_MOST_ONCE);
         System.setProperty("dexmaker.dexcache",
                 InstrumentationRegistry.getTargetContext().getCacheDir().getPath());
         Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierDisplayNameResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierDisplayNameResolverTest.java
index 2ccfe0c..17fb829 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierDisplayNameResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierDisplayNameResolverTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 
@@ -271,4 +272,14 @@
         assertThat(data.shouldShowPlmn()).isTrue();
         assertThat(data.getPlmn()).isEqualTo(HOME_PLMN_NUMERIC);
     }
+
+    @Test
+    public void testCarrierDisplayNameData_enforceNonNullDataSpn() {
+        try {
+            CarrierDisplayNameData cdnd = new CarrierDisplayNameData.Builder()
+                    .setSpn("testSpn").build();
+            fail("Expected IAE");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java
index 06b63a2..1c58ef2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierPrivilegesTrackerTest.java
@@ -52,6 +52,7 @@
 import android.net.Uri;
 import android.os.PersistableBundle;
 import android.os.Process;
+import android.os.UserHandle;
 import android.service.carrier.CarrierService;
 import android.telephony.CarrierConfigManager;
 import android.telephony.TelephonyManager;
@@ -187,9 +188,15 @@
             pkg.packageName = pkgCertInfo.pkgName;
             pkg.signatures = new Signature[] {new Signature(pkgCertInfo.cert)};
 
-            when(mPackageManager.getPackageInfo(
-                    eq(pkgCertInfo.pkgName), eq(PM_FLAGS)))
-                    .thenReturn(pkg);
+            if (mFeatureFlags.supportCarrierServicesForHsum()) {
+                when(mPackageManager.getPackageInfoAsUser(
+                        eq(pkgCertInfo.pkgName), eq(PM_FLAGS), anyInt()))
+                        .thenReturn(pkg);
+            } else {
+                when(mPackageManager.getPackageInfo(
+                        eq(pkgCertInfo.pkgName), eq(PM_FLAGS)))
+                        .thenReturn(pkg);
+            }
             when(mPackageManager.getPackageUidAsUser(
                     eq(pkgCertInfo.pkgName), eq(pkgCertInfo.userInfo.id)))
                     .thenReturn(pkgCertInfo.uid);
@@ -214,7 +221,8 @@
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
         CarrierPrivilegesTracker cpt =
-                new CarrierPrivilegesTracker(mTestableLooper.getLooper(), mPhone, mContext);
+                new CarrierPrivilegesTracker(
+                        mTestableLooper.getLooper(), mPhone, mContext, mFeatureFlags);
         verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
                 listenerArgumentCaptor.capture());
         mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(0);
@@ -579,8 +587,14 @@
 
         ResolveInfo pkg1ResolveInfo = new ResolveInfoBuilder().setActivity(PACKAGE_1).build();
         ResolveInfo pkg2ResolveInfo = new ResolveInfoBuilder().setActivity(PACKAGE_2).build();
-        when(mPackageManager.queryBroadcastReceivers(any(), anyInt())).thenReturn(
-                List.of(pkg1ResolveInfo, pkg2ResolveInfo));
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            when(mPackageManager.queryBroadcastReceiversAsUser(any(), anyInt(),
+                    anyInt())).thenReturn(
+                    List.of(pkg1ResolveInfo, pkg2ResolveInfo));
+        } else {
+            when(mPackageManager.queryBroadcastReceivers(any(), anyInt())).thenReturn(
+                    List.of(pkg1ResolveInfo, pkg2ResolveInfo));
+        }
 
         // SIM is READY
         sendSimCardStateChangedIntent(PHONE_ID, SIM_STATE_READY);
@@ -712,8 +726,13 @@
         // Update PACKAGE_1 to have no signatures
         PackageInfo pkg = new PackageInfo();
         pkg.packageName = PACKAGE_1;
-        when(mPackageManager.getPackageInfo(eq(PACKAGE_1), eq(PM_FLAGS)))
-                .thenReturn(pkg);
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            when(mPackageManager.getPackageInfoAsUser(eq(PACKAGE_1), eq(PM_FLAGS), anyInt()))
+                    .thenReturn(pkg);
+        } else {
+            when(mPackageManager.getPackageInfo(eq(PACKAGE_1), eq(PM_FLAGS)))
+                    .thenReturn(pkg);
+        }
 
         sendPackageChangedIntent(Intent.ACTION_PACKAGE_ADDED, PACKAGE_1);
         mTestableLooper.processAllMessages();
@@ -784,19 +803,35 @@
         setupInstalledPackages(
                 new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1),
                 new PackageCertInfo(PACKAGE_2, CERT_2, USER_1, UID_2));
-        when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
-        when(mPackageManager.getPackageUid(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
+        } else {
+            when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+            when(mPackageManager.getPackageUid(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
+        }
         ResolveInfo resolveInfoPkg1 = new ResolveInfoBuilder().setService(PACKAGE_1).build();
-        doReturn(List.of(resolveInfoPkg1))
-                .when(mPackageManager)
-                .queryIntentServices(any(), anyInt());
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            doReturn(List.of(resolveInfoPkg1))
+                    .when(mPackageManager)
+                    .queryIntentServicesAsUser(any(), anyInt(), anyInt());
+        } else {
+            doReturn(List.of(resolveInfoPkg1))
+                    .when(mPackageManager)
+                    .queryIntentServices(any(), anyInt());
+        }
         mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
 
         // Package_1 is disabled
         when(mPackageManager.getApplicationEnabledSetting(eq(PACKAGE_1))).thenReturn(
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
-        doReturn(List.of()).when(
-                mPackageManager).queryIntentServices(any(), anyInt());
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            doReturn(List.of()).when(
+                    mPackageManager).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+        } else {
+            doReturn(List.of()).when(
+                    mPackageManager).queryIntentServices(any(), anyInt());
+        }
         sendPackageChangedIntent(Intent.ACTION_PACKAGE_CHANGED, PACKAGE_1);
         mTestableLooper.processAllMessages();
 
@@ -807,8 +842,13 @@
         // Package_1 is re-enabled
         when(mPackageManager.getApplicationEnabledSetting(eq(PACKAGE_1))).thenReturn(
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
-        doReturn(List.of(resolveInfoPkg1)).when(
-                mPackageManager).queryIntentServices(any(), anyInt());
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            doReturn(List.of(resolveInfoPkg1)).when(
+                    mPackageManager).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+        } else {
+            doReturn(List.of(resolveInfoPkg1)).when(
+                    mPackageManager).queryIntentServices(any(), anyInt());
+        }
         sendPackageChangedIntent(Intent.ACTION_PACKAGE_CHANGED, PACKAGE_1);
         mTestableLooper.processAllMessages();
 
@@ -888,24 +928,45 @@
 
         ResolveInfo privilegeBroadcast = new ResolveInfoBuilder().setActivity(PACKAGE_1).build();
         ResolveInfo noPrivilegeBroadcast = new ResolveInfoBuilder().setActivity(PACKAGE_2).build();
-        when(mPackageManager.queryBroadcastReceivers(any(), anyInt())).thenReturn(
-                List.of(privilegeBroadcast, noPrivilegeBroadcast));
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            when(mPackageManager.queryBroadcastReceiversAsUser(any(), anyInt(),
+                    anyInt())).thenReturn(
+                    List.of(privilegeBroadcast, noPrivilegeBroadcast));
+        } else {
+            when(mPackageManager.queryBroadcastReceivers(any(), anyInt())).thenReturn(
+                    List.of(privilegeBroadcast, noPrivilegeBroadcast));
+        }
 
         ResolveInfo privilegeActivity = new ResolveInfoBuilder().setActivity(PACKAGE_3).build();
         ResolveInfo noPrivilegeActivity = new ResolveInfoBuilder().setActivity(PACKAGE_4).build();
-        when(mPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(
-                List.of(privilegeActivity, noPrivilegeActivity));
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            when(mPackageManager.queryIntentActivitiesAsUser(any(), anyInt(), anyInt())).thenReturn(
+                    List.of(privilegeActivity, noPrivilegeActivity));
+        } else {
+            when(mPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(
+                    List.of(privilegeActivity, noPrivilegeActivity));
+        }
 
         ResolveInfo privilegeService = new ResolveInfoBuilder().setService(PACKAGE_5).build();
         ResolveInfo noPrivilegeService = new ResolveInfoBuilder().setService(PACKAGE_6).build();
         // Use doReturn instead of when/thenReturn which has NPE with unknown reason
-        doReturn(List.of(privilegeService, noPrivilegeService)).when(
-                mPackageManager).queryIntentServices(any(), anyInt());
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            doReturn(List.of(privilegeService, noPrivilegeService)).when(
+                    mPackageManager).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+        } else {
+            doReturn(List.of(privilegeService, noPrivilegeService)).when(
+                    mPackageManager).queryIntentServices(any(), anyInt());
+        }
 
         ResolveInfo privilegeProvider = new ResolveInfoBuilder().setProvider(PACKAGE_7).build();
         ResolveInfo noPrivilegeProvider = new ResolveInfoBuilder().setProvider(PACKAGE_8).build();
-        when(mPackageManager.queryIntentContentProviders(any(), anyInt())).thenReturn(
-                List.of(privilegeProvider, noPrivilegeProvider));
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            when(mPackageManager.queryIntentContentProvidersAsUser(any(), anyInt(), anyInt()))
+                    .thenReturn(List.of(privilegeProvider, noPrivilegeProvider));
+        } else {
+            when(mPackageManager.queryIntentContentProviders(any(), anyInt())).thenReturn(
+                    List.of(privilegeProvider, noPrivilegeProvider));
+        }
 
         mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
         Intent intent = new Intent(CarrierService.CARRIER_SERVICE_INTERFACE);
@@ -934,11 +995,19 @@
         ResolveInfo privilegeService = new ResolveInfoBuilder().setService(PACKAGE_1).build();
         ResolveInfo noPrivilegeService = new ResolveInfoBuilder().setService(PACKAGE_2).build();
         // Use doReturn instead of when/thenReturn which has NPE with unknown reason
-        doReturn(List.of(privilegeService, noPrivilegeService)).when(
-                mPackageManager).queryIntentServices(any(), anyInt());
-        when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
-        when(mPackageManager.getPackageUid(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
-        when(mPackageManager.getPackageUid(eq(PACKAGE_3), anyInt())).thenReturn(UID_1);
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            doReturn(List.of(privilegeService, noPrivilegeService)).when(
+                    mPackageManager).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_3), anyInt())).thenReturn(UID_1);
+        } else {
+            doReturn(List.of(privilegeService, noPrivilegeService)).when(
+                    mPackageManager).queryIntentServices(any(), anyInt());
+            when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+            when(mPackageManager.getPackageUid(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
+            when(mPackageManager.getPackageUid(eq(PACKAGE_3), anyInt())).thenReturn(UID_1);
+        }
 
         // Get CS package name for the first time
         mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
@@ -947,7 +1016,11 @@
         mTestableLooper.processAllMessages();
 
         // Package manager should be queried from
-        verify(mPackageManager).queryIntentServices(any(), anyInt());
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            verify(mPackageManager).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+        } else {
+            verify(mPackageManager).queryIntentServices(any(), anyInt());
+        }
         assertEquals(PACKAGE_1, carrierServicePackageName);
         assertEquals(UID_1, carrierServiceUid);
 
@@ -958,7 +1031,11 @@
         mTestableLooper.processAllMessages();
 
         // It should return the same result, but didn't query package manager
-        verify(mPackageManager, never()).queryIntentServices(any(), anyInt());
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            verify(mPackageManager, never()).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+        } else {
+            verify(mPackageManager, never()).queryIntentServices(any(), anyInt());
+        }
         assertEquals(PACKAGE_1, carrierServicePackageName);
         assertEquals(UID_1, carrierServiceUid);
     }
@@ -978,12 +1055,21 @@
         ResolveInfo service1 = new ResolveInfoBuilder().setService(PACKAGE_1).build();
         ResolveInfo service2 = new ResolveInfoBuilder().setService(PACKAGE_2).build();
         // Use doReturn instead of when/thenReturn which has NPE with unknown reason
-        doReturn(List.of(service1, service2))
-                .when(mPackageManager)
-                .queryIntentServices(any(), anyInt());
-        when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
-        when(mPackageManager.getPackageUid(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
-        when(mPackageManager.getPackageUid(eq(PACKAGE_3), anyInt())).thenReturn(UID_1);
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            doReturn(List.of(service1, service2))
+                    .when(mPackageManager)
+                    .queryIntentServicesAsUser(any(), anyInt(), anyInt());
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_3), anyInt())).thenReturn(UID_1);
+        } else {
+            doReturn(List.of(service1, service2))
+                    .when(mPackageManager)
+                    .queryIntentServices(any(), anyInt());
+            when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+            when(mPackageManager.getPackageUid(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
+            when(mPackageManager.getPackageUid(eq(PACKAGE_3), anyInt())).thenReturn(UID_1);
+        }
 
         // Verify that neither carrier service (no privileges, or carrier-config based privileges)
         // are accepted.
@@ -992,7 +1078,11 @@
         int carrierServiceUid = mCarrierPrivilegesTracker.getCarrierServicePackageUid();
         mTestableLooper.processAllMessages();
 
-        verify(mPackageManager).queryIntentServices(any(), anyInt());
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            verify(mPackageManager).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+        } else {
+            verify(mPackageManager).queryIntentServices(any(), anyInt());
+        }
         assertNull(carrierServicePackageName);
         assertEquals(Process.INVALID_UID, carrierServiceUid);
     }
@@ -1008,11 +1098,19 @@
                 new PackageCertInfo(PACKAGE_3, CERT_1, USER_1, UID_1));
         // No CarrierService declared at all
         // Use doReturn instead of when/thenReturn which has NPE with unknown reason
-        doReturn(List.of()).when(
-                mPackageManager).queryIntentServices(any(), anyInt());
-        when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
-        when(mPackageManager.getPackageUid(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
-        when(mPackageManager.getPackageUid(eq(PACKAGE_3), anyInt())).thenReturn(UID_1);
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            doReturn(List.of()).when(
+                    mPackageManager).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_3), anyInt())).thenReturn(UID_1);
+        } else {
+            doReturn(List.of()).when(
+                    mPackageManager).queryIntentServices(any(), anyInt());
+            when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+            when(mPackageManager.getPackageUid(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
+            when(mPackageManager.getPackageUid(eq(PACKAGE_3), anyInt())).thenReturn(UID_1);
+        }
 
         mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
         String carrierServicePackageName = mCarrierPrivilegesTracker.getCarrierServicePackageName();
@@ -1021,7 +1119,11 @@
 
         assertNull(carrierServicePackageName);
         assertEquals(Process.INVALID_UID, carrierServiceUid);
-        verify(mPackageManager).queryIntentServices(any(), anyInt());
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            verify(mPackageManager).queryIntentServicesAsUser(any(), anyInt(), anyInt());
+        } else {
+            verify(mPackageManager).queryIntentServices(any(), anyInt());
+        }
     }
 
     @Test
@@ -1030,10 +1132,17 @@
         setupInstalledPackages(new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1));
         ResolveInfo carrierService = new ResolveInfoBuilder().setService(PACKAGE_1).build();
 
-        doReturn(List.of(carrierService))
-                .when(mPackageManager)
-                .queryIntentServices(any(), anyInt());
-        when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            doReturn(List.of(carrierService))
+                    .when(mPackageManager)
+                    .queryIntentServicesAsUser(any(), anyInt(), anyInt());
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+        } else {
+            doReturn(List.of(carrierService))
+                    .when(mPackageManager)
+                    .queryIntentServices(any(), anyInt());
+            when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+        }
 
         // Set override, and verify the carrier service package was not set due to a lack of a
         // matching cert.
@@ -1056,11 +1165,19 @@
         ResolveInfo service1 = new ResolveInfoBuilder().setService(PACKAGE_1).build();
         ResolveInfo service2 = new ResolveInfoBuilder().setService(PACKAGE_2).build();
 
-        doReturn(List.of(service1, service2))
-                .when(mPackageManager)
-                .queryIntentServices(any(), anyInt());
-        when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
-        when(mPackageManager.getPackageUid(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            doReturn(List.of(service1, service2))
+                    .when(mPackageManager)
+                    .queryIntentServicesAsUser(any(), anyInt(), anyInt());
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
+        } else {
+            doReturn(List.of(service1, service2))
+                    .when(mPackageManager)
+                    .queryIntentServices(any(), anyInt());
+            when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+            when(mPackageManager.getPackageUid(eq(PACKAGE_2), anyInt())).thenReturn(UID_2);
+        }
 
         mCarrierPrivilegesTracker = createCarrierPrivilegesTracker();
 
@@ -1093,10 +1210,17 @@
         setupInstalledPackages(new PackageCertInfo(PACKAGE_1, CERT_1, USER_1, UID_1));
         ResolveInfo carrierService = new ResolveInfoBuilder().setService(PACKAGE_1).build();
 
-        doReturn(List.of(carrierService))
-                .when(mPackageManager)
-                .queryIntentServices(any(), anyInt());
-        when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            doReturn(List.of(carrierService))
+                    .when(mPackageManager)
+                    .queryIntentServicesAsUser(any(), anyInt(), anyInt());
+            when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+        } else {
+            doReturn(List.of(carrierService))
+                    .when(mPackageManager)
+                    .queryIntentServices(any(), anyInt());
+            when(mPackageManager.getPackageUid(eq(PACKAGE_1), anyInt())).thenReturn(UID_1);
+        }
 
         // Set override, and expect that an invalid package name would not be selected as the
         // carrier config service.
@@ -1114,21 +1238,40 @@
     }
 
     private void sendSimCardStateChangedIntent(int phoneId, int simState) {
-        mContext.sendBroadcast(
-                new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED)
-                        .putExtra(EXTRA_SIM_STATE, simState)
-                        .putExtra(PhoneConstants.PHONE_KEY, phoneId));
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            mContext.sendBroadcastAsUser(
+                    new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED)
+                            .putExtra(EXTRA_SIM_STATE, simState)
+                            .putExtra(PhoneConstants.PHONE_KEY, phoneId), UserHandle.ALL);
+        } else {
+            mContext.sendBroadcast(
+                    new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED)
+                            .putExtra(EXTRA_SIM_STATE, simState)
+                            .putExtra(PhoneConstants.PHONE_KEY, phoneId));
+        }
     }
 
     private void sendSimApplicationStateChangedIntent(int phoneId, int simState) {
-        mContext.sendBroadcast(
-                new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED)
-                        .putExtra(EXTRA_SIM_STATE, simState)
-                        .putExtra(PhoneConstants.PHONE_KEY, phoneId));
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            mContext.sendBroadcastAsUser(
+                    new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED)
+                            .putExtra(EXTRA_SIM_STATE, simState)
+                            .putExtra(PhoneConstants.PHONE_KEY, phoneId), UserHandle.ALL);
+        } else {
+            mContext.sendBroadcast(
+                    new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED)
+                            .putExtra(EXTRA_SIM_STATE, simState)
+                            .putExtra(PhoneConstants.PHONE_KEY, phoneId));
+        }
     }
 
     private void sendPackageChangedIntent(String action, String pkgName) {
-        mContext.sendBroadcast(new Intent(action, new Uri.Builder().path(pkgName).build()));
+        if (mFeatureFlags.supportCarrierServicesForHsum()) {
+            mContext.sendBroadcastAsUser(
+                    new Intent(action, new Uri.Builder().path(pkgName).build()), UserHandle.ALL);
+        } else {
+            mContext.sendBroadcast(new Intent(action, new Uri.Builder().path(pkgName).build()));
+        }
     }
 
     /** Returns the SHA-1 hash (as a hex String) for the given hex String. */
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java
index a3fcd4e..753f85c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierResolverTest.java
@@ -98,7 +98,7 @@
         super.setUp(getClass().getSimpleName());
         ((MockContentResolver) mContext.getContentResolver()).addProvider(
                 CarrierId.AUTHORITY, new CarrierIdContentProvider());
-        mCarrierResolver = new CarrierResolver(mPhone);
+        mCarrierResolver = new CarrierResolver(mPhone, mFeatureFlags);
         mCarrierResolver.sendEmptyMessage(ICC_CHANGED_EVENT);
         processAllMessages();
         logd("CarrierResolverTest -Setup!");
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index 4612ad9..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);
@@ -541,6 +549,12 @@
         }
 
         @Override
+        public void sendBroadcastAsUser(Intent intent, UserHandle user,
+                String receiverPermission, Bundle options) {
+            sendBroadcast(intent);
+        }
+
+        @Override
         public void sendBroadcastMultiplePermissions(Intent intent,
                 String[] includePermissions, String[] excludePermissions) {
             sendBroadcast(intent);
@@ -681,6 +695,9 @@
 
         @Override
         public void startActivity(Intent intent) {}
+
+        @Override
+        public void startActivityAsUser(Intent intent, UserHandle user) {}
     }
 
     private final Multimap<String, ComponentName> mComponentNamesByAction =
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
index 8209f92..8720171 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
@@ -23,7 +23,6 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.telephony.CellIdentityGsm;
 import android.telephony.CellInfo;
@@ -101,23 +100,7 @@
     }
 
     @Test @SmallTest
-    public void testNotifyDataActivity() throws Exception {
-        when(mFeatureFlags.notifyDataActivityChangedWithSlot()).thenReturn(false);
-        //mock data activity state
-        doReturn(TelephonyManager.DATA_ACTIVITY_NONE).when(mPhone).getDataActivityState();
-        mDefaultPhoneNotifierUT.notifyDataActivity(mPhone);
-        verify(mTelephonyRegistryManager).notifyDataActivityChanged(eq(0),
-                eq(TelephonyManager.DATA_ACTIVITY_NONE));
-
-        doReturn(1).when(mPhone).getSubId();
-        doReturn(TelephonyManager.DATA_ACTIVITY_IN).when(mPhone).getDataActivityState();
-        mDefaultPhoneNotifierUT.notifyDataActivity(mPhone);
-        verify(mTelephonyRegistryManager).notifyDataActivityChanged(eq(1),
-                eq(TelephonyManager.DATA_ACTIVITY_IN));
-    }
-    @Test @SmallTest
     public void testNotifyDataActivityWithSlot() throws Exception {
-        when(mFeatureFlags.notifyDataActivityChangedWithSlot()).thenReturn(true);
         //mock data activity state
         doReturn(TelephonyManager.DATA_ACTIVITY_NONE).when(mPhone).getDataActivityState();
         doReturn(PHONE_ID).when(mPhone).getPhoneId();
@@ -395,10 +378,75 @@
 
     @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);
         verify(mTelephonyRegistryManager).notifyCarrierRoamingNtnModeChanged(
                 eq(subId), eq(true));
     }
+
+    @Test
+    @SmallTest
+    public void testCarrierRoamingNtnEligibleStateChanged() {
+        int subId = mPhone.getSubId();
+        mDefaultPhoneNotifierUT.notifyCarrierRoamingNtnEligibleStateChanged(mPhone, true);
+        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));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
index a13a92c..c923f69 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/FakeTelephonyProvider.java
@@ -131,11 +131,14 @@
                     + Telephony.SimInfo.COLUMN_SATELLITE_ENABLED + " INTEGER DEFAULT 0,"
                     + Telephony.SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER
                     + " INTEGER DEFAULT 1, "
-                    + Telephony.SimInfo.COLUMN_IS_NTN + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_IS_ONLY_NTN + " INTEGER DEFAULT 0,"
                     + Telephony.SimInfo.COLUMN_SERVICE_CAPABILITIES + " INTEGER DEFAULT 7,"
                     + Telephony.SimInfo.COLUMN_TRANSFER_STATUS + " INTEGER DEFAULT 0,"
                     + Telephony.SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS + " INTEGER DEFAULT 0,"
-                    + Telephony.SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS + " TEXT"
+                    + Telephony.SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS + " TEXT,"
+                    + Telephony.SimInfo.COLUMN_SATELLITE_ESOS_SUPPORTED + " INTEGER DEFAULT 0,"
+                    + Telephony.SimInfo.COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM
+                    + " INTEGER DEFAULT 0"
                     + ");";
         }
 
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/GsmCdmaConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java
index 45f8c12..e56ac90 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaConnectionTest.java
@@ -21,10 +21,12 @@
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.emergency.EmergencyNumber;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -313,4 +315,52 @@
         assertEquals(DisconnectCause.OUT_OF_SERVICE,
                 connection.disconnectCauseFromCode(CallFailCause.LOCAL_SERVICE_UNAVAILABLE));
     }
+
+    @Test
+    public void testUpdateEmergencyRouting() {
+        Bundle extras = new Bundle();
+        extras.putBoolean(PhoneConstants.EXTRA_USE_EMERGENCY_ROUTING, true);
+
+        DialArgs dialArgs = new DialArgs.Builder()
+                .setIsEmergency(true)
+                .setIntentExtras(extras)
+                .build();
+
+        doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+
+        connection = new GsmCdmaConnection(mPhone, "911", mCT, null, dialArgs);
+        // Not updated when category is unset.
+        assertEquals(getTestEmergencyNumber(), connection.getEmergencyNumberInfo());
+
+        extras.putInt(PhoneConstants.EXTRA_EMERGENCY_SERVICE_CATEGORY,
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED);
+
+        dialArgs = new DialArgs.Builder()
+                .setIsEmergency(true)
+                .setIntentExtras(extras)
+                .build();
+
+        connection = new GsmCdmaConnection(mPhone, "911", mCT, null, dialArgs);
+        // Not updated when category is EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED.
+        assertEquals(getTestEmergencyNumber(), connection.getEmergencyNumberInfo());
+
+        extras.putInt(PhoneConstants.EXTRA_EMERGENCY_SERVICE_CATEGORY,
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE);
+
+        dialArgs = new DialArgs.Builder()
+                .setIsEmergency(true)
+                .setIntentExtras(extras)
+                .build();
+
+        connection = new GsmCdmaConnection(mPhone, "911", mCT, null, dialArgs);
+
+        EmergencyNumber expectedNumber = new EmergencyNumber("911", "us", "30",
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE,
+                new ArrayList<String>(), EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+
+        // Updated when category is not EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED.
+        assertNotEquals(getTestEmergencyNumber(), connection.getEmergencyNumberInfo());
+        assertEquals(expectedNumber, connection.getEmergencyNumberInfo());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index ba08f8b..7735c97 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -2018,6 +2018,42 @@
 
     @Test
     @SmallTest
+    public void testUsageSettingUpdate_ResetToDefault() {
+        setupUsageSettingResources();
+        mPhoneUT.mCi = mMockCi;
+
+        SubscriptionInfoInternal si = makeSubscriptionInfoInternal(
+                false, SubscriptionManager.USAGE_SETTING_DATA_CENTRIC);
+        doReturn(si).when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+
+        mPhoneUT.updateUsageSetting();
+        processAllMessages();
+
+        verify(mMockCi).getUsageSetting(any());
+        mPhoneUT.sendMessage(mPhoneUT.obtainMessage(GsmCdmaPhone.EVENT_GET_USAGE_SETTING_DONE,
+                new AsyncResult(null,
+                        new int[]{SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC}, null)));
+        processAllMessages();
+
+        // Grab the message to ensure it returns the preferred value for updating the cache
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockCi).setUsageSetting(
+                messageCaptor.capture(), eq(SubscriptionManager.USAGE_SETTING_DATA_CENTRIC));
+        AsyncResult.forMessage(messageCaptor.getValue());
+        messageCaptor.getValue().sendToTarget();
+        processAllMessages();
+
+        si = makeSubscriptionInfoInternal(false, SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC);
+        doReturn(si).when(mSubscriptionManagerService).getSubscriptionInfoInternal(anyInt());
+
+        mPhoneUT.updateUsageSetting();
+        processAllMessages();
+
+        verify(mMockCi).setUsageSetting(any(), eq(SubscriptionManager.USAGE_SETTING_VOICE_CENTRIC));
+    }
+
+    @Test
+    @SmallTest
     public void testUsageSettingUpdate_DefaultOpportunistic() {
         setupUsageSettingResources();
         mPhoneUT.mCi = mMockCi;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
index f64155c..6c84b08 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ImsSmsDispatcherTest.java
@@ -34,6 +34,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.os.Binder;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SmsMessage;
@@ -71,6 +72,7 @@
     PersistableBundle mBundle = new PersistableBundle();
     private static final int SUB_0 = 0;
     private static final String TAG = "ImsSmsDispatcherTest";
+    private int mCallingUserId;
 
     @Before
     public void setUp() throws Exception {
@@ -94,6 +96,7 @@
         mTrackerData = new HashMap<>(1);
         when(mSmsTracker.getData()).thenReturn(mTrackerData);
         verify(mSmsDispatchersController).setImsManager(mImsManager);
+        mCallingUserId = Binder.getCallingUserHandle().getIdentifier();
     }
 
     @After
@@ -333,8 +336,7 @@
         doReturn(mSmsUsageMonitor).when(mSmsDispatchersController).getUsageMonitor();
 
         mImsSmsDispatcher.sendText("+15555551212", null, "MessageRef test",
-                null, null, null, null, false,
-                -1, false, -1, false, 0);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0);
         verify(mImsManager).sendSms(eq(token + 1), eq(messageRef), eq(SmsMessage.FORMAT_3GPP),
                 nullable(String.class), eq(false), (byte[]) any());
     }
@@ -349,8 +351,7 @@
         doReturn(mSmsUsageMonitor).when(mSmsDispatchersController).getUsageMonitor();
 
         mImsSmsDispatcher.sendText("+15555551212", null, "MessageRef test",
-                null, null, null, null, false,
-                -1, false, -1, false, 0);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0);
         verify(mImsManager).sendSms(eq(token + 1), eq(messageRef), eq(SmsMessage.FORMAT_3GPP),
                 nullable(String.class), eq(false), (byte[]) any());
 
@@ -384,8 +385,7 @@
         doReturn(mSmsUsageMonitor).when(mSmsDispatchersController).getUsageMonitor();
 
         mImsSmsDispatcher.sendText("+15555551212", null, "MessageRef test",
-                null, null, null, null, false,
-                -1, false, -1, false, 0);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0);
         verify(mImsManager).sendSms(eq(token + 1), eq(messageRef), eq(SmsMessage.FORMAT_3GPP),
                 nullable(String.class), eq(false), (byte[]) any());
 
@@ -423,8 +423,7 @@
         when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
         doReturn(mSmsUsageMonitor).when(mSmsDispatchersController).getUsageMonitor();
         mImsSmsDispatcher.sendText("+15555551212", null, "Retry test",
-                null, null, null, null, false,
-                -1, false, -1, false, 0);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0);
         verify(mImsManager).sendSms(eq(token + 1), eq(messageRef), eq(SmsMessage.FORMAT_3GPP),
                 nullable(String.class), eq(false), (byte[]) any());
         assertEquals(2, mImsSmsDispatcher.getMaxRetryCountOverIms());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
index e08abd9..f92643a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NetworkTypeControllerTest.java
@@ -1507,6 +1507,52 @@
     }
 
     @Test
+    public void testTransitionToNrIdle() throws Exception {
+        doReturn(true).when(mFeatureFlags).supportNrSaRrcIdle();
+        doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
+        doReturn(ServiceState.FREQUENCY_RANGE_HIGH).when(mServiceState).getNrFrequencyRange();
+        ArrayList<PhysicalChannelConfig> physicalChannelConfigs = new ArrayList<>();
+        // use advanced band
+        physicalChannelConfigs.add(new PhysicalChannelConfig.Builder()
+                .setPhysicalCellId(1)
+                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                .setBand(41)
+                .build());
+        doReturn(physicalChannelConfigs).when(mSST).getPhysicalChannelConfigList();
+        mBundle.putIntArray(CarrierConfigManager.KEY_ADDITIONAL_NR_ADVANCED_BANDS_INT_ARRAY,
+                new int[]{41});
+        mBundle.putString(CarrierConfigManager.KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING,
+                "connected_mmwave,any,10");
+        sendCarrierConfigChanged();
+
+        assertEquals("connected_mmwave", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+
+        // empty PCC, switch to connected_rrc_idle,
+        // isNrAdvanced is still true(due to either advance band OR frequency)
+        physicalChannelConfigs.clear();
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, physicalChannelConfigs, null));
+        processAllMessages();
+
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+        assertTrue(mNetworkTypeController.areAnyTimersActive());
+
+        // Received an event update, verify should stay in idle because physical link didn't change,
+        // otherwise there is a loop between advance state and idle state.
+        mNetworkTypeController.sendMessage(0 /* EVENT_UPDATE */);
+        processAllMessages();
+
+        assertEquals("connected_rrc_idle", getCurrentState().getName());
+        assertEquals(TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED,
+                mNetworkTypeController.getOverrideNetworkType());
+    }
+
+    @Test
     public void testSecondaryTimerAdvanceBand() throws Exception {
         doReturn(true).when(mFeatureFlags).supportNrSaRrcIdle();
         doReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED).when(mServiceState).getNrState();
@@ -1603,8 +1649,23 @@
                 mNetworkTypeController.getOverrideNetworkType());
         assertTrue(mNetworkTypeController.areAnyTimersActive());
 
+
+        // the timer has been reduced from 20 - 6s(advance band) to 5s(regular). Suppose passed 1s,
+        // a new PCC shouldn't affect the timer.
+        moveTimeForward(1 * 1000);
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, Collections.emptyList(), null));
+        mNetworkTypeController.sendMessage(11 /* EVENT_PHYSICAL_CHANNEL_CONFIGS_CHANGED */,
+                new AsyncResult(null, List.of(
+                        new PhysicalChannelConfig.Builder()
+                                .setPhysicalCellId(3)
+                                .setNetworkType(TelephonyManager.NETWORK_TYPE_NR)
+                                .setCellConnectionStatus(CellInfo.CONNECTION_PRIMARY_SERVING)
+                                .build()), null));
+        processAllMessages();
+
         // Verify the timer has been reduced from 20 - 6s(advance band) to 5s(regular).
-        moveTimeForward(5 * 1000);
+        moveTimeForward(4 * 1000);
         processAllMessages();
 
         assertEquals("connected_rrc_idle", getCurrentState().getName());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
index 0e04aff..b09d90d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
@@ -203,26 +203,40 @@
         for (int i : enabledLogicalSlots) { expectedSlots.add(i); }
         assertEquals(expectedSlots, mPcm.getSlotsSupportingSimultaneousCellularCalls());
     }
+    @Test
+    @SmallTest
+    public void testUpdateSimultaneousCallingSupportBothInvalidSlotIds() throws Exception {
+        // Test case where both slot IDs are invalid (-1 and 5).
+        testUpdateSimultaneousCallingSupportWithInvalidSlots(Arrays.asList(-1, 5));
+    }
 
     @Test
     @SmallTest
-    public void testUpdateSimultaneousCallingSupport_invalidResponse_shouldFail() throws Exception {
-        doReturn(false).when(mFeatureFlags).simultaneousCallingIndications();
-        init(2);
-        mPcm.updateSimultaneousCallingSupport();
+    public void testUpdateSimultaneousCallingSupportOneInvalidSlotId() throws Exception {
+        // Test case where one slot ID is valid (1) and the other is invalid (2).
+        testUpdateSimultaneousCallingSupportWithInvalidSlots(Arrays.asList(1, 2));
+    }
 
-        // Have the modem send invalid phone slots -1 and 5:
-        List<Integer> invalidEnabledLogicalSlots = Arrays.asList(-1, 5);
-        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
-        verify(mMockRadioConfig).updateSimultaneousCallingSupport(captor.capture());
-        Message msg = captor.getValue();
-        AsyncResult.forMessage(msg, invalidEnabledLogicalSlots, null);
-        msg.sendToTarget();
-        processAllMessages();
+    @Test
+    @SmallTest
+    public void testUpdateSimultaneousCallingSupportInvalidExtraSlotId() throws Exception {
+        // Test case where the number of slot IDs exceeds the phone count (2) and one slot ID is
+        // invalid (2).
+        testUpdateSimultaneousCallingSupportWithInvalidSlots(Arrays.asList(0, 1, 2));
+    }
 
-        // We would expect to DSDA to be disabled and mSlotsSupportingSimultaneousCellularCalls to
-        // have been cleared:
-        assertTrue(mPcm.getSlotsSupportingSimultaneousCellularCalls().isEmpty());
+    @Test
+    @SmallTest
+    public void testUpdateSimultaneousCallingSupportInvalidSingularSlotId() throws Exception {
+        // Test case where only a single, invalid slot ID (0) is provided.
+        testUpdateSimultaneousCallingSupportWithInvalidSlots(List.of(0));
+    }
+
+    @Test
+    @SmallTest
+    public void testUpdateSimultaneousCallingSupportInvalidEmptySlotIds() throws Exception {
+        // Test case where an empty list of slot IDs is provided.
+        testUpdateSimultaneousCallingSupportWithInvalidSlots(List.of());
     }
 
     /**
@@ -576,4 +590,28 @@
 
         assertEquals(capability, mPcm.getStaticPhoneCapability());
     }
+
+    private void testUpdateSimultaneousCallingSupportWithInvalidSlots(List<Integer> invalidSlots)
+            throws Exception {
+        doReturn(false).when(mFeatureFlags).simultaneousCallingIndications();
+        init(2);
+        mPcm.updateSimultaneousCallingSupport();
+
+        sendInvalidSlotsToModem(invalidSlots);
+        processAllMessages();
+
+        assertDsdaDisabledAndSlotsCleared();
+    }
+
+    private void sendInvalidSlotsToModem(List<Integer> invalidSlots) {
+        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
+        verify(mMockRadioConfig).updateSimultaneousCallingSupport(captor.capture());
+        Message msg = captor.getValue();
+        AsyncResult.forMessage(msg, invalidSlots, null);
+        msg.sendToTarget();
+    }
+
+    private void assertDsdaDisabledAndSlotsCleared() {
+        assertTrue(mPcm.getSlotsSupportingSimultaneousCellularCalls().isEmpty());
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
index bf9ced3..7a30984 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -628,6 +628,7 @@
     public void testFormatSingaporeInternational() {
         // Disable feature flag.
         mSetFlagsRule.disableFlags(Flags.FLAG_REMOVE_COUNTRY_CODE_FROM_LOCAL_SINGAPORE_CALLS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_NATIONAL_COUNTRY_CODE_FORMATTING_FOR_LOCAL_CALLS);
 
         // International call from a US iso
         assertEquals("+65 6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "US"));
@@ -636,6 +637,7 @@
         assertEquals("+65 6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "us"));
 
         // Enable feature flag
+        mSetFlagsRule.disableFlags(Flags.FLAG_NATIONAL_COUNTRY_CODE_FORMATTING_FOR_LOCAL_CALLS);
         mSetFlagsRule.enableFlags(Flags.FLAG_REMOVE_COUNTRY_CODE_FROM_LOCAL_SINGAPORE_CALLS);
 
         // Internal call from a US iso
@@ -644,6 +646,7 @@
         // Lowercase country iso
         assertEquals("+65 6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "us"));
         mSetFlagsRule.disableFlags(Flags.FLAG_REMOVE_COUNTRY_CODE_FROM_LOCAL_SINGAPORE_CALLS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_NATIONAL_COUNTRY_CODE_FORMATTING_FOR_LOCAL_CALLS);
     }
 
     /**
@@ -655,14 +658,16 @@
     public void testFormatSingaporeNational() {
         // Disable feature flag.
         mSetFlagsRule.disableFlags(Flags.FLAG_REMOVE_COUNTRY_CODE_FROM_LOCAL_SINGAPORE_CALLS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_NATIONAL_COUNTRY_CODE_FORMATTING_FOR_LOCAL_CALLS);
 
         // Local call from a Singaporean number to a Singaporean number
-        assertEquals("+65 6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "SG"));
+        assertEquals("6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "SG"));
 
         // Lowercase country iso.
-        assertEquals("+65 6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "sg"));
+        assertEquals("6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "sg"));
 
         // Enable feature flag.
+        mSetFlagsRule.disableFlags(Flags.FLAG_NATIONAL_COUNTRY_CODE_FORMATTING_FOR_LOCAL_CALLS);
         mSetFlagsRule.enableFlags(Flags.FLAG_REMOVE_COUNTRY_CODE_FROM_LOCAL_SINGAPORE_CALLS);
 
         // Local call from a Singaporean number to a Singaporean number.
@@ -671,6 +676,44 @@
         // Lowercase country iso.
         assertEquals("6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "sg"));
         mSetFlagsRule.disableFlags(Flags.FLAG_REMOVE_COUNTRY_CODE_FROM_LOCAL_SINGAPORE_CALLS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_NATIONAL_COUNTRY_CODE_FORMATTING_FOR_LOCAL_CALLS);
+    }
+
+    @SmallTest
+    @Test
+    public void testFormatTaiwanNational() {
+        // Disable feature flag.
+        mSetFlagsRule.disableFlags(Flags.FLAG_NATIONAL_COUNTRY_CODE_FORMATTING_FOR_LOCAL_CALLS);
+        assertEquals("+886 2 8729 6000", PhoneNumberUtils.formatNumber("+886287296000", "TW"));
+        assertEquals("+886 2 8729 6000", PhoneNumberUtils.formatNumber("+886287296000", "tw"));
+        assertEquals("+886 988 102 544", PhoneNumberUtils.formatNumber("+886988102544", "TW"));
+        assertEquals("+886 988 102 544", PhoneNumberUtils.formatNumber("+886988102544", "tw"));
+
+        // Enable feature flag.
+        mSetFlagsRule.enableFlags(Flags.FLAG_NATIONAL_COUNTRY_CODE_FORMATTING_FOR_LOCAL_CALLS);
+        assertEquals("02 8729 6000", PhoneNumberUtils.formatNumber("+886287296000", "TW"));
+        assertEquals("02 8729 6000", PhoneNumberUtils.formatNumber("+886287296000", "tw"));
+        assertEquals("0988 102 544", PhoneNumberUtils.formatNumber("+886988102544", "TW"));
+        assertEquals("0988 102 544", PhoneNumberUtils.formatNumber("+886988102544", "tw"));
+        mSetFlagsRule.disableFlags(Flags.FLAG_NATIONAL_COUNTRY_CODE_FORMATTING_FOR_LOCAL_CALLS);
+    }
+
+    @SmallTest
+    @Test
+    public void testFormatTaiwanInternational() {
+        // Disable feature flag.
+        mSetFlagsRule.disableFlags(Flags.FLAG_NATIONAL_COUNTRY_CODE_FORMATTING_FOR_LOCAL_CALLS);
+        assertEquals("+886 2 8729 6000", PhoneNumberUtils.formatNumber("+886287296000", "US"));
+        assertEquals("+886 2 8729 6000", PhoneNumberUtils.formatNumber("+886287296000", "us"));
+        assertEquals("+886 988 102 544", PhoneNumberUtils.formatNumber("+886988102544", "US"));
+        assertEquals("+886 988 102 544", PhoneNumberUtils.formatNumber("+886988102544", "us"));
+
+        mSetFlagsRule.enableFlags(Flags.FLAG_NATIONAL_COUNTRY_CODE_FORMATTING_FOR_LOCAL_CALLS);
+        assertEquals("+886 2 8729 6000", PhoneNumberUtils.formatNumber("+886287296000", "US"));
+        assertEquals("+886 2 8729 6000", PhoneNumberUtils.formatNumber("+886287296000", "us"));
+        assertEquals("+886 988 102 544", PhoneNumberUtils.formatNumber("+886988102544", "US"));
+        assertEquals("+886 988 102 544", PhoneNumberUtils.formatNumber("+886988102544", "us"));
+        mSetFlagsRule.disableFlags(Flags.FLAG_NATIONAL_COUNTRY_CODE_FORMATTING_FOR_LOCAL_CALLS);
     }
 
     @SmallTest
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/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index e3da458..70bdcba 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.nullable;
@@ -594,7 +595,7 @@
         sst.setRadioPowerForReason(false, false, false, false, reason);
         assertTrue(sst.getRadioPowerOffReasons().contains(reason));
         assertTrue(sst.getRadioPowerOffReasons().size() == 1);
-        verify(mSatelliteController).onCellularRadioPowerOffRequested();
+        verify(mSatelliteController).onSetCellularRadioPowerStateRequested(eq(false));
         clearInvocations(mSatelliteController);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF);
@@ -602,7 +603,7 @@
                 TelephonyManager.RADIO_POWER_REASON_USER);
         assertTrue(sst.getRadioPowerOffReasons().contains(reason));
         assertTrue(sst.getRadioPowerOffReasons().size() == 1);
-        verify(mSatelliteController, never()).onCellularRadioPowerOffRequested();
+        verify(mSatelliteController, never()).onSetCellularRadioPowerStateRequested(anyBoolean());
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_OFF);
 
@@ -610,7 +611,7 @@
         // had been turned off for.
         sst.setRadioPowerForReason(true, false, false, false, reason);
         assertTrue(sst.getRadioPowerOffReasons().isEmpty());
-        verify(mSatelliteController, never()).onCellularRadioPowerOffRequested();
+        verify(mSatelliteController).onSetCellularRadioPowerStateRequested(eq(true));
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
 
@@ -1928,6 +1929,8 @@
         sst.setRadioPower(false);
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertTrue(mSimulatedCommands.getRadioState() == TelephonyManager.RADIO_POWER_ON);
+        verify(mSatelliteController).onSetCellularRadioPowerStateRequested(eq(false));
+        verify(mSatelliteController).onPowerOffCellularRadioFailed();
         sst.requestShutdown();
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
         assertFalse(mSimulatedCommands.getRadioState()
@@ -2980,7 +2983,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();
@@ -3002,7 +3005,7 @@
         sst.mSS = ss;
 
         // update the spn
-        sst.updateSpnDisplay();
+        sst.updateCarrierDisplayName();
 
         // Plmn should be shown, and the string is "No service"
         Bundle b = getExtrasFromLastSpnUpdateIntent();
@@ -3023,7 +3026,7 @@
         sst.mSS = ss;
 
         // update the spn
-        sst.updateSpnDisplay();
+        sst.updateCarrierDisplayName();
 
         // Plmn should be shown, and the string is null
         Bundle b = getExtrasFromLastSpnUpdateIntent();
@@ -3046,7 +3049,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);
@@ -3082,12 +3085,13 @@
         doReturn(mImsPhone).when(mPhone).getImsPhone();
         doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM).when(mImsPhone)
                 .getImsRegistrationTech();
+        doReturn(true).when(mPhone).isImsRegistered();
         String[] formats = {CROSS_SIM_CALLING_VOICE_FORMAT, "%s"};
         Resources r = mContext.getResources();
         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);
@@ -3124,7 +3128,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);
@@ -3157,7 +3161,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];
@@ -3183,7 +3187,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);
@@ -3236,7 +3240,70 @@
         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();
+        assertThat(b.getString(TelephonyManager.EXTRA_PLMN))
+                .isEqualTo(CARRIER_NAME_DISPLAY_NO_SERVICE);
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_PLMN)).isTrue();
+    }
+
+    @Test
+    public void testUpdateSpnDisplayLegacy_CrossSimCallingDataOOS_displayOOS() {
+        mBundle.putBoolean(
+                CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL, false);
+        sendCarrierConfigUpdate(PHONE_ID);
+
+        // GSM phone
+        doReturn(true).when(mPhone).isPhoneTypeGsm();
+
+        // Reg tech is Cross Sim but both data and voice are OOS
+        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState();
+        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getDataRegistrationState();
+        doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mServiceState).getDataNetworkType();
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM).when(
+                mImsPhone).getImsRegistrationTech();
+        sst.mSS = mServiceState;
+
+        // wifi-calling is enabled
+        doReturn(true).when(mPhone).isWifiCallingEnabled();
+
+        // update the spn
+        sst.updateCarrierDisplayName();
+
+        // Plmn should be shown, and the string is "No service"
+        Bundle b = getExtrasFromLastSpnUpdateIntent();
+        assertThat(b.getString(TelephonyManager.EXTRA_PLMN))
+                .isEqualTo(CARRIER_NAME_DISPLAY_NO_SERVICE);
+        assertThat(b.getBoolean(TelephonyManager.EXTRA_SHOW_PLMN)).isTrue();
+    }
+
+    @Test
+    public void testUpdateSpnDisplayLegacy_CrossSimCallingVoiceOOS_displayOOS() {
+        mBundle.putBoolean(
+                CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL, false);
+        sendCarrierConfigUpdate(PHONE_ID);
+
+        // GSM phone
+        doReturn(true).when(mPhone).isPhoneTypeGsm();
+
+        // voice out of service but data in service (connected to Cross Sim IWLAN)
+        doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState();
+        doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getDataRegistrationState();
+        doReturn(TelephonyManager.NETWORK_TYPE_IWLAN).when(mServiceState).getDataNetworkType();
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM).when(
+                mImsPhone).getImsRegistrationTech();
+        doReturn(false).when(mPhone).isImsRegistered();
+        sst.mSS = mServiceState;
+
+        // wifi-calling is enabled
+        doReturn(true).when(mPhone).isWifiCallingEnabled();
+
+        // update the spn
+        sst.updateCarrierDisplayName();
 
         // Plmn should be shown, and the string is "No service"
         Bundle b = getExtrasFromLastSpnUpdateIntent();
@@ -3436,7 +3503,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());
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java
index 879b184..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);
     }
 
@@ -254,7 +253,7 @@
 
     /**
      * Test that simultaneous calling is not supported when IMS is not registered and cellular
-     * simultaneous calling is only supported for one SIM subscription.
+     * simultaneous calling is not supported for any SIM subscriptions.
      */
     @Test
     @SmallTest
@@ -264,8 +263,8 @@
         init(2);
         setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
 
-        // Have the modem inform telephony that only phone slot 0 supports DSDA:
-        List<Integer> enabledLogicalSlots = List.of(0);
+        // Have the modem inform telephony that no phone slots currently support DSDA:
+        List<Integer> enabledLogicalSlots = List.of();
         setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
 
         // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java
index 06dbd0b..710f1ec 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java
@@ -31,6 +31,7 @@
 
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.Build;
 import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
@@ -67,6 +68,7 @@
     private SmsController mSmsControllerUT;
     private final String smscAddrStr = "+1206313004";
     private String mCallingPackage;
+    private int mCallingUserId;
 
     @Before
     public void setUp() throws Exception {
@@ -74,6 +76,7 @@
         mAdnRecordCache = Mockito.mock(AdnRecordCache.class);
         mSmsControllerUT = new SmsController(mContext, mFeatureFlags);
         mCallingPackage = mContext.getOpPackageName();
+        mCallingUserId = Binder.getCallingUserHandle().getIdentifier();
 
         doReturn(true).when(mPackageManager).hasSystemFeature(
                 PackageManager.FEATURE_TELEPHONY_MESSAGING);
@@ -196,9 +199,9 @@
         doReturn(true).when(mSubscriptionManager)
                 .isSubscriptionAssociatedWithUser(eq(subId), any());
 
-        mSmsControllerUT.sendVisualVoicemailSmsForSubscriber(mCallingPackage,null ,
-                subId, null, 0, null, null);
-        verify(mIccSmsInterfaceManager).sendTextWithSelfPermissions(any(),
+        mSmsControllerUT.sendVisualVoicemailSmsForSubscriber(mCallingPackage, mCallingUserId,
+                null , subId, null, 0, null, null);
+        verify(mIccSmsInterfaceManager).sendTextWithSelfPermissions(any(), eq(mCallingUserId),
                 any(), any(), any(), any(), any(), any(), eq(false), eq(true));
     }
 
@@ -206,10 +209,11 @@
     public void sendVisualVoicemailSmsForSubscriber_phoneIsInEcm() {
         doReturn(true).when(mPhone).isInEcm();
 
-        mSmsControllerUT.sendVisualVoicemailSmsForSubscriber(mCallingPackage,null ,
-                1, null, 0, null, null);
+        mSmsControllerUT.sendVisualVoicemailSmsForSubscriber(mCallingPackage, mCallingUserId,
+                null , 1, null, 0, null, null);
         verify(mIccSmsInterfaceManager, never()).sendTextWithSelfPermissions(any(),
-                any(), any(), any(), any(), any(), any(), eq(false), eq(true));
+                eq(mCallingUserId), any(), any(), any(), any(), any(), any(),
+                eq(false), eq(true));
 
         doReturn(false).when(mPhone).isInEcm();
     }
@@ -223,7 +227,8 @@
         mSmsControllerUT.sendTextForSubscriber(subId, mCallingPackage, null, "1234",
                 null, "text", null, null, false, 0L, true, true);
         verify(mIccSmsInterfaceManager, Mockito.times(1))
-                .sendText(mCallingPackage, "1234", null, "text", null, null, false, 0L, true);
+                .sendText(mCallingPackage, mCallingUserId,
+                        "1234", null, "text", null, null, false, 0L, true);
     }
 
     @Test
@@ -239,7 +244,8 @@
         mSmsControllerUT.sendTextForSubscriber(subId, mCallingPackage, null, "1234",
                 null, "text", null, null, false, 0L, true, true);
         verify(mIccSmsInterfaceManager, Mockito.times(1))
-                .sendText(mCallingPackage, "1234", null, "text", null, null, false, 0L, true);
+                .sendText(mCallingPackage, mCallingUserId,
+                        "1234", null, "text", null, null, false, 0L, true);
     }
 
     @Test
@@ -255,7 +261,8 @@
         mSmsControllerUT.sendTextForSubscriber(subId, mCallingPackage, null, "1234",
                 null, "text", null, null, false, 0L, true, true);
         verify(mIccSmsInterfaceManager, Mockito.times(0))
-                .sendText(mCallingPackage, "1234", null, "text", null, null, false, 0L, true);
+                .sendText(mCallingPackage, mCallingUserId,
+                        "1234", null, "text", null, null, false, 0L, true);
     }
 
     @Test
@@ -320,6 +327,7 @@
         mSmsControllerUT.sendTextForSubscriber(subId, mCallingPackage, null, "1234",
                 null, "text", null, null, false, 0L, true, true);
         verify(mIccSmsInterfaceManager, Mockito.times(1))
-                .sendText(mCallingPackage, "1234", null, "text", null, null, false, 0L, true);
+                .sendText(mCallingPackage, mCallingUserId,
+                        "1234", null, "text", null, null, false, 0L, true);
     }
 }
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
index 414fb3b..53ecac3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony;
 
 import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE;
+import static com.android.internal.telephony.SmsDispatchersController.PendingRequest;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -24,10 +25,12 @@
 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;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
@@ -64,6 +67,7 @@
 import com.android.internal.telephony.domainselection.SmsDomainSelectionConnection;
 import com.android.internal.telephony.emergency.EmergencyStateTracker;
 import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.satellite.DatagramDispatcher;
 import com.android.internal.telephony.uicc.IccUtils;
 
 import org.junit.After;
@@ -94,35 +98,42 @@
             return getDomainSelectionConnectionHolder(emergency);
         }
 
-        public void testSendData(String callingPackage, String destAddr, String scAddr,
-                int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
-                boolean isForVvm) {
-            sendData(callingPackage, destAddr, scAddr,
+        public void testSendData(String callingPackage, int callingUser,
+                String destAddr, String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
+                PendingIntent deliveryIntent, boolean isForVvm) {
+            sendData(callingPackage, callingUser, destAddr, scAddr,
                     destPort, data, sentIntent, deliveryIntent, isForVvm);
         }
 
         public void testSendMultipartText(String destAddr, String scAddr,
                 ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
                 ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
-                boolean persistMessage, int priority, boolean expectMore, int validityPeriod,
-                long messageId) {
+                int callingUser, boolean persistMessage, int priority, boolean expectMore,
+                int validityPeriod, long messageId) {
             sendMultipartText(destAddr, scAddr, parts, sentIntents, deliveryIntents, messageUri,
-                    callingPkg, persistMessage, priority, expectMore, validityPeriod, messageId);
+                    callingPkg, callingUser, persistMessage, priority, expectMore,
+                    validityPeriod, messageId);
         }
 
         public void testNotifySmsSentToEmergencyStateTracker(String destAddr, long messageId,
                 boolean isOverIms, boolean isLastSmsPart) {
-            notifySmsSentToEmergencyStateTracker(destAddr, messageId, isOverIms, isLastSmsPart);
+            notifySmsSent(getSmsTracker(destAddr, messageId), isOverIms,
+                isLastSmsPart, true/*success*/);
         }
 
         public void testNotifySmsSentFailedToEmergencyStateTracker(String destAddr,
                 long messageId, boolean isOverIms) {
-            notifySmsSentFailedToEmergencyStateTracker(destAddr, messageId, isOverIms);
+            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);
+        }
     }
 
     /**
@@ -134,11 +145,11 @@
         }
 
         @Override
-        public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
-                byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
-                boolean isForVvm) {
-            super.sendData(callingPackage, destAddr, scAddr, destPort,
-                    data, sentIntent, deliveryIntent, isForVvm);
+        public void sendData(String callingPackage, int callingUser, String destAddr,
+                String scAddr, int destPort, byte[] data, PendingIntent sentIntent,
+                PendingIntent deliveryIntent, boolean isForVvm, long uniqueMessageId) {
+            super.sendData(callingPackage, callingUser, destAddr, scAddr, destPort,
+                    data, sentIntent, deliveryIntent, isForVvm, uniqueMessageId);
         }
 
         @Override
@@ -161,11 +172,11 @@
         }
 
         @Override
-        public void sendData(String callingPackage, String destAddr, String scAddr, int destPort,
-                byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
-                boolean isForVvm) {
-            super.sendData(callingPackage, destAddr, scAddr, destPort,
-                    data, sentIntent, deliveryIntent, isForVvm);
+        public void sendData(String callingPackage, int callingUser, String destAddr, String scAddr,
+                int destPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent,
+                boolean isForVvm, long uniqueMessageId) {
+            super.sendData(callingPackage, callingUser, destAddr, scAddr, destPort,
+                    data, sentIntent, deliveryIntent, isForVvm, uniqueMessageId);
         }
 
         @Override
@@ -191,15 +202,21 @@
     private TestSmsDispatchersController mSmsDispatchersController;
     private boolean mInjectionCallbackTriggered = false;
     private CompletableFuture<Integer> mDscFuture;
+    private DatagramDispatcher mMockDatagramDispatcher;
+    private int mCallingUserId;
 
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         mTracker = mock(SMSDispatcher.SmsTracker.class);
         mFeatureFlags = mock(FeatureFlags.class);
+        mMockDatagramDispatcher = mock(DatagramDispatcher.class);
+        replaceInstance(DatagramDispatcher.class, "sInstance", null,
+                mMockDatagramDispatcher);
         setupMockPackagePermissionChecks();
         mSmsDispatchersController = new TestSmsDispatchersController(mPhone, mSmsStorageMonitor,
             mSmsUsageMonitor, mTestableLooper.getLooper(), mFeatureFlags);
+        mCallingUserId = mContext.getUserId();
         processAllMessages();
     }
 
@@ -234,7 +251,7 @@
     @Test @SmallTest
     public void testReportSmsMemoryStatus() throws Exception {
         int eventReportMemoryStatusDone = 3;
-        SmsStorageMonitor smsStorageMonnitor = new SmsStorageMonitor(mPhone);
+        SmsStorageMonitor smsStorageMonnitor = new SmsStorageMonitor(mPhone, mFeatureFlags);
         Message result = smsStorageMonnitor.obtainMessage(eventReportMemoryStatusDone);
         ImsSmsDispatcher mImsSmsDispatcher = Mockito.mock(ImsSmsDispatcher.class);
         mSmsDispatchersController.setImsSmsDispatcher(mImsSmsDispatcher);
@@ -247,7 +264,7 @@
     @Test @SmallTest
     public void testReportSmsMemoryStatusFailure() throws Exception {
         int eventReportMemoryStatusDone = 3;
-        SmsStorageMonitor smsStorageMonnitor = new SmsStorageMonitor(mPhone);
+        SmsStorageMonitor smsStorageMonnitor = new SmsStorageMonitor(mPhone, mFeatureFlags);
         Message result = smsStorageMonnitor.obtainMessage(eventReportMemoryStatusDone);
         mSmsDispatchersController.setImsSmsDispatcher(null);
         mSmsDispatchersController.reportSmsMemoryStatus(result);
@@ -259,7 +276,7 @@
     public void testSendImsGmsTest() throws Exception {
         switchImsSmsFormat(PhoneConstants.PHONE_TYPE_GSM);
         mSmsDispatchersController.sendText("111"/* desAddr*/, "222" /*scAddr*/, TAG,
-                null, null, null, null, false, -1, false, -1, false, 0L);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
         verify(mSimulatedCommandsVerifier).sendImsGsmSms(eq("038122F2"),
                 eq("0100038111F100001CD3F69C989EC3C3F431BA2C9F0FDF6EBAFCCD6697E5D4F29C0E"), eq(0), eq(0),
                 any(Message.class));
@@ -269,7 +286,7 @@
     public void testSendImsGmsTestWithOutDesAddr() throws Exception {
         switchImsSmsFormat(PhoneConstants.PHONE_TYPE_GSM);
         mSmsDispatchersController.sendText(null, "222" /*scAddr*/, TAG,
-                null, null, null, null, false, -1, false, -1, false, 0L);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
         verify(mSimulatedCommandsVerifier, times(0)).sendImsGsmSms(anyString(), anyString(),
                 anyInt(), anyInt(), any(Message.class));
     }
@@ -278,7 +295,7 @@
     public void testSendImsCdmaTest() throws Exception {
         switchImsSmsFormat(PhoneConstants.PHONE_TYPE_CDMA);
         mSmsDispatchersController.sendText("111"/* desAddr*/, "222" /*scAddr*/, TAG,
-                null, null, null, null, false, -1, false, -1, false, 0L);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
         verify(mSimulatedCommandsVerifier).sendImsCdmaSms((byte[])any(), eq(0), eq(0),
                 any(Message.class));
     }
@@ -353,7 +370,8 @@
 
         mSmsDispatchersController.sendText("111", null /*scAddr*/, TAG,
                 null, null, null, "com.android.messaging",
-                false, -1, false, -1, false, 0L);
+                mContext.getUserId(), false, -1, false,
+                -1, false, 0L);
         byte[] smscbyte = PhoneNumberUtils.networkPortionToCalledPartyBCDWithLength(
                 "222");
         String smsc = IccUtils.bytesToHexString(smscbyte);
@@ -462,7 +480,7 @@
         setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
 
         mSmsDispatchersController.sendText("911", "2222", "text", mSentIntent, null, null,
-                "test-app", false, 0, false, 10, false, 1L, false);
+                "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
         processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
@@ -479,8 +497,8 @@
 
         verify(mEmergencySmsDsc).finishSelection();
         verify(mImsSmsDispatcher).sendText(eq("911"), eq("2222"), eq("text"), eq(mSentIntent),
-                any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false),
-                eq(1L), eq(false));
+                any(), any(), eq("test-app"), eq(mCallingUserId), 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());
@@ -494,7 +512,7 @@
         setUpEmergencyStateTracker(DisconnectCause.OUT_OF_SERVICE);
 
         mSmsDispatchersController.sendText("911", "2222", "text", mSentIntent, null, null,
-                "test-app", false, 0, false, 10, false, 1L, false);
+                "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
         processAllMessages();
 
         // Verify the domain selection requested regardless of the result of EmergencyStateTracker.
@@ -512,7 +530,7 @@
         ArrayList<PendingIntent> sentIntents = new ArrayList<>();
         ArrayList<PendingIntent> deliveryIntents = new ArrayList<>();
         mSmsDispatchersController.testSendMultipartText("911", "2222", parts, sentIntents,
-                deliveryIntents, null, "test-app", false, 0, false, 10, 1L);
+                deliveryIntents, null, "test-app", mCallingUserId, false, 0, false, 10, 1L);
         processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
@@ -529,8 +547,9 @@
 
         verify(mEmergencySmsDsc).finishSelection();
         verify(mImsSmsDispatcher).sendMultipartText(eq("911"), eq("2222"), eq(parts),
-                eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"), eq(false), eq(0),
-                eq(false), eq(10), eq(1L));
+                eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"),
+                eq(mCallingUserId), eq(false), eq(0), eq(false),
+                eq(10), eq(1L), anyLong());
         assertNull(holder.getConnection());
         assertFalse(holder.isDomainSelectionRequested());
         assertEquals(0, holder.getPendingRequests().size());
@@ -703,7 +722,7 @@
         when(mImsSmsDispatcher.isAvailable()).thenReturn(true);
 
         mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
-                "test-app", false, 0, false, 10, false, 1L, false);
+                "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
         processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
@@ -728,8 +747,8 @@
         assertEquals(0, holder.getPendingRequests().size());
 
         verify(mImsSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent),
-                any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false),
-                eq(1L), eq(false));
+                any(), any(), eq("test-app"), eq(mCallingUserId),
+                eq(false), eq(0), eq(false), eq(10), eq(false), eq(1L), eq(false), anyLong());
     }
 
     @Test
@@ -743,7 +762,8 @@
         when(mImsSmsDispatcher.isEmergencySmsSupport(anyString())).thenReturn(true);
 
         mSmsDispatchersController.sendText("911", "2222", "text", mSentIntent, null, null,
-                "test-app", false, 0, false, 10, false, 1L, false);
+                "test-app", mCallingUserId, false, 0, false,
+                10, false, 1L, false);
         processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
@@ -768,8 +788,8 @@
         assertEquals(0, holder.getPendingRequests().size());
 
         verify(mImsSmsDispatcher).sendText(eq("911"), eq("2222"), eq("text"), eq(mSentIntent),
-                any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false),
-                eq(1L), eq(false));
+                any(), any(), eq("test-app"), eq(0), eq(false), eq(0), eq(false), eq(10),
+                eq(false), eq(1L), eq(false), anyLong());
     }
 
     @Test
@@ -779,7 +799,7 @@
         setUpSmsDispatchers();
 
         mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
-                "test-app", false, 0, false, 10, false, 1L, false);
+                "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
         processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
@@ -790,7 +810,7 @@
         assertEquals(1, holder.getPendingRequests().size());
 
         mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
-                "test-app", false, 0, false, 10, false, 1L, false);
+                "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
         processAllMessages();
 
         verify(mSmsDsc).requestDomainSelection(any(), any());
@@ -802,9 +822,10 @@
         processAllMessages();
 
         verify(mSmsDsc).finishSelection();
-        verify(mImsSmsDispatcher, times(2)).sendText(eq("1111"), eq("2222"), eq("text"),
-                eq(mSentIntent), any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10),
-                eq(false), eq(1L), eq(false));
+        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), anyLong());
         assertNull(holder.getConnection());
         assertFalse(holder.isDomainSelectionRequested());
         assertEquals(0, holder.getPendingRequests().size());
@@ -819,13 +840,13 @@
         when(mImsSmsDispatcher.isAvailable()).thenReturn(true);
 
         mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
-                "test-app", false, 0, false, 10, false, 1L, false);
+                "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
 
         // Expect that the domain selection is not executed and
         // ImsSmsDispatcher handles this text directly.
         verify(mImsSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"),
-                eq(mSentIntent), any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10),
-                eq(false), eq(1L), eq(false));
+                eq(mSentIntent), any(), any(), eq("test-app"), eq(mCallingUserId), eq(false), eq(0),
+                eq(false), eq(10), eq(false), eq(1L), eq(false), anyLong());
     }
 
     @Test
@@ -835,7 +856,7 @@
         setUpSmsDispatchers();
 
         mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
-                "test-app", false, 0, false, 10, false, 1L, false);
+                "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
         processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
@@ -873,7 +894,7 @@
 
         // Expect that new domain selection connection is created and domain selection is performed.
         mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
-                "test-app", false, 0, false, 10, false, 1L, false);
+                "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
         processAllMessages();
 
         verify(mSmsDsc).finishSelection();
@@ -888,8 +909,8 @@
 
         verify(newSmsDsc).finishSelection();
         verify(mImsSmsDispatcher, times(2)).sendText(eq("1111"), eq("2222"), eq("text"),
-                eq(mSentIntent), any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10),
-                eq(false), eq(1L), eq(false));
+                eq(mSentIntent), any(), any(), eq("test-app"), eq(mCallingUserId), 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());
@@ -904,7 +925,7 @@
         setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
 
         mSmsDispatchersController.sendText("911", "2222", "text", mSentIntent, null, null,
-                "test-app", false, 0, false, 10, false, 1L, false);
+                "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
         processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
@@ -944,7 +965,7 @@
 
         // Expect that new domain selection connection is created and domain selection is performed.
         mSmsDispatchersController.sendText("911", "2222", "text", mSentIntent, null, null,
-                "test-app", false, 0, false, 10, false, 1L, false);
+                "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
         processAllMessages();
 
         verify(mEmergencySmsDsc).finishSelection();
@@ -959,8 +980,8 @@
 
         verify(newEmergencySmsDsc).finishSelection();
         verify(mImsSmsDispatcher, times(2)).sendText(eq("911"), eq("2222"), eq("text"),
-                eq(mSentIntent), any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10),
-                eq(false), eq(1L), eq(false));
+                eq(mSentIntent), any(), any(), eq("test-app"), eq(0), 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());
@@ -991,7 +1012,7 @@
         // Expect that creating a domain selection connection is failed and
         // fallback to the legacy implementation.
         mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
-                "test-app", false, 0, false, 10, false, 1L, false);
+                "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
         processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
@@ -1002,8 +1023,8 @@
         assertEquals(0, holder.getPendingRequests().size());
 
         verify(mImsSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent),
-                any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false),
-                eq(1L), eq(false));
+                any(), any(), eq("test-app"), eq(mCallingUserId), eq(false), eq(0),
+                eq(false), eq(10), eq(false), eq(1L), eq(false), anyLong());
     }
 
     @Test
@@ -1033,7 +1054,7 @@
         // Expect that creating a domain selection connection is failed and
         // fallback to the legacy implementation.
         mSmsDispatchersController.sendText("911", "2222", "text", mSentIntent, null, null,
-                "test-app", false, 0, false, 10, false, 1L, false);
+                "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
         processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
@@ -1045,8 +1066,8 @@
         assertEquals(0, holder.getPendingRequests().size());
 
         verify(mImsSmsDispatcher).sendText(eq("911"), eq("2222"), eq("text"), eq(mSentIntent),
-                any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false),
-                eq(1L), eq(false));
+                any(), any(), eq("test-app"), eq(mCallingUserId), eq(false), eq(0), eq(false),
+                eq(10), eq(false), eq(1L), eq(false), anyLong());
     }
 
     private void switchImsSmsFormat(int phoneType) {
@@ -1063,6 +1084,42 @@
         assertTrue(mSmsDispatchersController.setImsManager(imsManager));
     }
 
+    @Test
+    public void testSendSmsToDatagramDispatcher() {
+        when(mSatelliteController.isInCarrierRoamingNbIotNtn(any(Phone.class))).thenReturn(true);
+        mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
+                "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
+        processAllMessages();
+        verify(mMockDatagramDispatcher).sendSms(any());
+
+        clearInvocations(mMockDatagramDispatcher);
+        ArrayList<String> parts = new ArrayList<>();
+        ArrayList<PendingIntent> sentIntents = new ArrayList<>();
+        ArrayList<PendingIntent> deliveryIntents = new ArrayList<>();
+        mSmsDispatchersController.testSendMultipartText("1111", "2222", parts, sentIntents,
+                deliveryIntents, null, "test-app", mCallingUserId, false, 0, false, 10, 1L);
+        processAllMessages();
+        verify(mMockDatagramDispatcher).sendSms(any());
+    }
+
+    @Test
+    public void testSendCarrierRoamingNbIotNtnText() {
+        PendingRequest pendingRequest = createPendingRequest();
+        switchImsSmsFormat(PhoneConstants.PHONE_TYPE_GSM);
+
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(false);
+        mSmsDispatchersController.sendCarrierRoamingNbIotNtnText(pendingRequest);
+        processAllMessages();
+        verify(mSimulatedCommandsVerifier, times(0)).sendImsGsmSms(anyString(), anyString(),
+                anyInt(), anyInt(), any(Message.class));
+
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        mSmsDispatchersController.sendCarrierRoamingNbIotNtnText(pendingRequest);
+        processAllMessages();
+        verify(mSimulatedCommandsVerifier, times(1)).sendImsGsmSms(anyString(), anyString(),
+                anyInt(), anyInt(), any(Message.class));
+    }
+
     private void setUpDomainSelectionEnabled(boolean enabled) {
         mSmsDispatchersController.setDomainSelectionResolverProxy(
                 new SmsDispatchersController.DomainSelectionResolverProxy() {
@@ -1152,7 +1209,7 @@
 
         byte[] data = new byte[] { 0x01 };
         mSmsDispatchersController.testSendData(
-                "test-app", "1111", "2222", 8080, data, mSentIntent, null, false);
+                "test-app", mCallingUserId, "1111", "2222", 8080, data, mSentIntent, null, false);
         processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
@@ -1168,14 +1225,14 @@
 
         verify(mSmsDsc).finishSelection();
         if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
-            verify(mImsSmsDispatcher).sendData(eq("test-app"), eq("1111"), eq("2222"), eq(8080),
-                    eq(data), eq(mSentIntent), any(), eq(false));
+            verify(mImsSmsDispatcher).sendData(eq("test-app"), eq(0), eq("1111"), eq("2222"),
+                    eq(8080), eq(data), eq(mSentIntent), any(), eq(false), anyLong());
         } else if (isCdmaMo) {
-            verify(mCdmaSmsDispatcher).sendData(eq("test-app"), eq("1111"), eq("2222"), eq(8080),
-                    eq(data), eq(mSentIntent), any(), eq(false));
+            verify(mCdmaSmsDispatcher).sendData(eq("test-app"), eq(0), eq("1111"), eq("2222"),
+                    eq(8080), eq(data), eq(mSentIntent), any(), eq(false), anyLong());
         } else {
-            verify(mGsmSmsDispatcher).sendData(eq("test-app"), eq("1111"), eq("2222"), eq(8080),
-                    eq(data), eq(mSentIntent), any(), eq(false));
+            verify(mGsmSmsDispatcher).sendData(eq("test-app"), eq(0), eq("1111"), eq("2222"),
+                    eq(8080), eq(data), eq(mSentIntent), any(), eq(false), anyLong());
         }
         assertNull(holder.getConnection());
         assertFalse(holder.isDomainSelectionRequested());
@@ -1188,7 +1245,7 @@
         setUpSmsDispatchers();
 
         mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
-                "test-app", false, 0, false, 10, false, 1L, false);
+                "test-app", mCallingUserId, false, 0, false, 10, false, 1L, false);
         processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
@@ -1205,16 +1262,16 @@
         verify(mSmsDsc).finishSelection();
         if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
             verify(mImsSmsDispatcher).sendText(eq("1111"), eq("2222"), eq("text"), eq(mSentIntent),
-                    any(), any(), eq("test-app"), eq(false), eq(0), eq(false), eq(10), eq(false),
-                    eq(1L), eq(false));
+                    any(), any(), eq("test-app"), eq(0), eq(false), eq(0), eq(false), eq(10),
+                    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(false), eq(0), eq(false), eq(10), eq(false),
-                    eq(1L), eq(false));
+                    any(), any(), eq("test-app"), eq(0), eq(false), eq(0), eq(false), eq(10),
+                    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(false), eq(0), eq(false), eq(10), eq(false),
-                    eq(1L), eq(false));
+                    any(), any(), eq("test-app"), eq(0), eq(false), eq(0), eq(false), eq(10),
+                    eq(false), eq(1L), eq(false), anyLong());
         }
         assertNull(holder.getConnection());
         assertFalse(holder.isDomainSelectionRequested());
@@ -1230,7 +1287,7 @@
         ArrayList<PendingIntent> sentIntents = new ArrayList<>();
         ArrayList<PendingIntent> deliveryIntents = new ArrayList<>();
         mSmsDispatchersController.testSendMultipartText("1111", "2222", parts, sentIntents,
-                deliveryIntents, null, "test-app", false, 0, false, 10, 1L);
+                deliveryIntents, null, "test-app", mCallingUserId, false, 0, false, 10, 1L);
         processAllMessages();
 
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
@@ -1247,16 +1304,17 @@
         verify(mSmsDsc).finishSelection();
         if (domain == NetworkRegistrationInfo.DOMAIN_PS) {
             verify(mImsSmsDispatcher).sendMultipartText(eq("1111"), eq("2222"), eq(parts),
-                    eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"), eq(false), eq(0),
-                    eq(false), eq(10), eq(1L));
+                    eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"), eq(mCallingUserId),
+                    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(false), eq(0),
-                    eq(false), eq(10), eq(1L));
+                    eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"), eq(mCallingUserId),
+                    eq(false), eq(0),
+                    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(false), eq(0),
-                    eq(false), eq(10), eq(1L));
+                    eq(sentIntents), eq(deliveryIntents), any(), eq("test-app"), eq(mCallingUserId),
+                    eq(false), eq(0), eq(false), eq(10), eq(1L), anyLong());
         }
         assertNull(holder.getConnection());
         assertFalse(holder.isDomainSelectionRequested());
@@ -1321,4 +1379,18 @@
             verify(mGsmSmsDispatcher).sendSms(eq(mTracker));
         }
     }
+
+    private static <T> ArrayList<T> asArrayList(T object) {
+        ArrayList<T> list = new ArrayList<>();
+        list.add(object);
+        return list;
+    }
+
+    private PendingRequest createPendingRequest() {
+        return new PendingRequest(
+                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);
+    }
 }
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/SmsStorageMonitorTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java
index 4483c61..bdf7e78 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsStorageMonitorTest.java
@@ -52,7 +52,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
-        mSmsStorageMonitor = new SmsStorageMonitor(mPhone);
+        mSmsStorageMonitor = new SmsStorageMonitor(mPhone, mFeatureFlags);
         mSmsStorageMonitor.setMaxRetries(MAX_RETRIES);
         mSmsStorageMonitor.setRetryDelayInMillis(RETRY_DELAY);
         processAllMessages();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoTest.java
index ac92b8f..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)
@@ -69,6 +68,7 @@
                 .setServiceCapabilities(SubscriptionManager.getServiceCapabilitiesSet(
                     SubscriptionManager.SERVICE_CAPABILITY_DATA_BITMASK))
                 .setTransferStatus(1)
+                .setSatelliteESOSSupported(true)
                 .build();
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyCountryDetectorTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyCountryDetectorTest.java
index a2763fe..5db1206 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyCountryDetectorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyCountryDetectorTest.java
@@ -39,6 +39,8 @@
 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.Looper;
 import android.os.Message;
@@ -48,6 +50,9 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.telephony.flags.FeatureFlags;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -72,6 +77,8 @@
     LocaleTracker mMockLocaleTracker2;
     @Mock Location mMockLocation;
     @Mock Network mMockNetwork;
+    @Mock
+    private FeatureFlags mMockFeatureFlags;
 
     @Captor
     private ArgumentCaptor<LocationListener> mLocationListenerCaptor;
@@ -105,25 +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);
+                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
@@ -137,8 +146,10 @@
         clearInvocations(mLocationManager);
         clearInvocations(mConnectivityManager);
         when(mMockLocaleTracker.getCurrentCountry()).thenReturn("US");
-        TelephonyCountryDetector inst1 = TelephonyCountryDetector.getInstance(mContext);
-        TelephonyCountryDetector inst2 = TelephonyCountryDetector.getInstance(mContext);
+        TelephonyCountryDetector inst1 = TelephonyCountryDetector
+                .getInstance(mContext, mMockFeatureFlags);
+        TelephonyCountryDetector inst2 = TelephonyCountryDetector
+                .getInstance(mContext, mMockFeatureFlags);
         assertEquals(inst1, inst2);
         if (isGeoCoderImplemented()) {
             verify(mLocationManager, never()).requestLocationUpdates(anyString(), anyLong(),
@@ -275,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));
@@ -284,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(),
@@ -291,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();
@@ -301,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));
@@ -314,14 +334,87 @@
         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);
+
+        mCountryDetectorUT.registerForWifiConnectivityStateChanged(listener,
+                1 /* EVENT_WIFI_CONNECTIVITY_STATE_CHANGED*/, null);
+
+        // Wi-fi becomes available
+        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 lost
+        clearInvocations(mLocationManager);
+        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();
     }
@@ -357,8 +450,9 @@
          * @param locationManager  The LocationManager instance.
          */
         TestTelephonyCountryDetector(Looper looper, Context context,
-                LocationManager locationManager, ConnectivityManager connectivityManager) {
-            super(looper, context, locationManager, connectivityManager);
+                LocationManager locationManager, ConnectivityManager connectivityManager,
+                FeatureFlags featureFlags) {
+            super(looper, context, locationManager, connectivityManager, featureFlags);
         }
 
         @Override
@@ -375,4 +469,37 @@
             return WAIT_FOR_LOCATION_UPDATE_REQUEST_QUOTA_RESET_TIMEOUT_MILLIS;
         }
     }
+
+    private static class WifiConnectivityStateChangedListener extends Handler {
+        private static final int EVENT_WIFI_CONNECTIVITY_STATE_CHANGED = 1;
+        private final Object mIsWifiConnectedLock = new Object();
+        @GuardedBy("mIsWifiConnectedLock")
+        private boolean mIsWifiConnected = false;
+
+        WifiConnectivityStateChangedListener(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_WIFI_CONNECTIVITY_STATE_CHANGED: {
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    synchronized (mIsWifiConnectedLock) {
+                        mIsWifiConnected = (boolean) ar.result;
+                    }
+                    break;
+                }
+
+                default:
+                    break;
+            }
+        }
+
+        public boolean getIsWifiConnected() {
+            synchronized (mIsWifiConnectedLock) {
+                return mIsWifiConnected;
+            }
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
index d4717dd..ac89501 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
@@ -37,6 +37,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.permission.LegacyPermissionManager;
@@ -558,6 +559,38 @@
                 UserHandle.SYSTEM));
     }
 
+    @Test
+    public void testIsSystemOrPhone_systemUser() {
+        assertTrue(TelephonyPermissions.isSystemOrPhone(Process.SYSTEM_UID));
+        assertTrue(TelephonyPermissions.isSystemOrPhone(Process.PHONE_UID));
+
+        assertFalse(TelephonyPermissions.isSystemOrPhone(1002));
+    }
+
+    @Test
+    public void testIsSystemOrPhone_nonSystemUser() {
+        assertTrue(TelephonyPermissions.isSystemOrPhone(1001000));
+        assertTrue(TelephonyPermissions.isSystemOrPhone(1001001));
+
+        assertFalse(TelephonyPermissions.isSystemOrPhone(1001002));
+    }
+
+    @Test
+    public void testIsRootOrShell_systemUser() {
+        assertTrue(TelephonyPermissions.isRootOrShell(Process.ROOT_UID));
+        assertTrue(TelephonyPermissions.isRootOrShell(Process.SHELL_UID));
+
+        assertFalse(TelephonyPermissions.isRootOrShell(1002));
+    }
+
+    @Test
+    public void testIsRootOrShell_nonSystemUser() {
+        assertTrue(TelephonyPermissions.isRootOrShell(1000000));
+        assertTrue(TelephonyPermissions.isRootOrShell(1002000));
+
+        assertFalse(TelephonyPermissions.isRootOrShell(1001002));
+    }
+
     // Put mMockTelephony into service cache so that TELEPHONY_SUPPLIER will get it.
     private void setTelephonyMockAsService() throws Exception {
         when(mMockTelephonyBinder.queryLocalInterface(anyString())).thenReturn(mMockTelephony);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
index 3e447a9..1f5a26b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
@@ -45,6 +45,8 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation;
 import android.telephony.BarringInfo;
@@ -75,13 +77,16 @@
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.server.TelephonyRegistry;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 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;
@@ -94,6 +99,8 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class TelephonyRegistryTest extends TelephonyTest {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     // Mocked classes
     private SubscriptionInfo mMockSubInfo;
     private TelephonyRegistry.ConfigurationProvider mMockConfigurationProvider;
@@ -117,7 +124,11 @@
     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 List<Integer> mCarrierRoamingNtnAvailableServices;
 
     // All events contribute to TelephonyRegistry#isPhoneStatePermissionRequired
     private static final Set<Integer> READ_PHONE_STATE_EVENTS;
@@ -194,6 +205,7 @@
             TelephonyCallback.RegistrationFailedListener,
             TelephonyCallback.DataActivityListener,
             TelephonyCallback.SimultaneousCellularCallingSupportListener,
+            TelephonyCallback.EmergencyCallbackModeListener,
             TelephonyCallback.CarrierRoamingNtnModeListener {
         // This class isn't mockable to get invocation counts because the IBinder is null and
         // crashes the TelephonyRegistry. Make a cheesy verify(times()) alternative.
@@ -291,10 +303,43 @@
         }
 
         @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;
         }
+
+        @Override
+        public void onCarrierRoamingNtnEligibleStateChanged(boolean eligible) {
+            invocationCount.incrementAndGet();
+            mCarrierRoamingNtnEligible = eligible;
+        }
+
+        @Override
+        public void onCarrierRoamingNtnAvailableServicesChanged(List<Integer> services) {
+            invocationCount.incrementAndGet();
+            mCarrierRoamingNtnAvailableServices = services;
+        }
     }
 
     private void addTelephonyRegistryService() {
@@ -329,6 +374,10 @@
         mContextFixture.putStringArrayResource(
                 com.android.internal.R.array.config_serviceStateLocationAllowedPackages,
                 new String[0]);
+
+        UserInfo userInfo = new UserInfo(UserHandle.myUserId(), "" /* name */, 0 /* flags */);
+        doReturn(userInfo.id).when(mIActivityManager).getCurrentUserId();
+
         processAllMessages();
         assertEquals(mTelephonyRegistry.asBinder(),
                 ServiceManager.getService("telephony.registry"));
@@ -964,6 +1013,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(
@@ -1060,6 +1111,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(
@@ -1572,6 +1625,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());
@@ -1585,4 +1689,39 @@
         processAllMessages();
         assertTrue(mCarrierRoamingNtnMode);
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public void testNotifyCarrierRoamingNtnEligibleStateChanged() {
+        int subId = INVALID_SUBSCRIPTION_ID;
+        doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
+        doReturn(0/*slotIndex*/).when(mMockSubInfo).getSimSlotIndex();
+        int[] events = {TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED};
+
+        mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, true);
+
+        mTelephonyRegistry.notifyCarrierRoamingNtnEligibleStateChanged(subId, true);
+        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();
+        int[] carrierServices = mCarrierRoamingNtnAvailableServices.stream()
+                .mapToInt(Integer::intValue).toArray();
+        assertTrue(Arrays.equals(carrierServices, services));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 38b4f77..d80c9a2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -580,6 +580,9 @@
         mNullCipherNotifier = Mockito.mock(NullCipherNotifier.class);
 
         doReturn(true).when(mFeatureFlags).minimalTelephonyCdmCheck();
+        doReturn(true).when(mFeatureFlags).supportNetworkProvider();
+        doReturn(true).when(mFeatureFlags).hsumBroadcast();
+        doReturn(true).when(mFeatureFlags).hsumPackageManager();
 
         TelephonyManager.disableServiceHandleCaching();
         PropertyInvalidatedCache.disableForTestMode();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java b/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
index c3db35c..58e5617 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/WapPushOverSmsTest.java
@@ -30,17 +30,23 @@
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Telephony;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.telephony.flags.FeatureFlags;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
 
 public class WapPushOverSmsTest extends TelephonyTest {
     // Mocked classes
@@ -48,6 +54,9 @@
 
     private WapPushOverSms mWapPushOverSmsUT;
 
+    @Mock private FeatureFlags mFeatureFlags;
+    private static final UserHandle MOCKED_MAIN_USER = UserHandle.of(10);
+
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
@@ -56,9 +65,14 @@
         // Note that this replaces only cached services in ServiceManager. If a service is not found
         // in the cache, a real instance is used.
         mServiceManagerMockedServices.put("isms", mISmsStub);
+        mFeatureFlags = Mockito.mock(FeatureFlags.class);
+        doReturn(true).when(mFeatureFlags).smsMmsDeliverBroadcastsRedirectToMainUser();
         doReturn(mISmsStub).when(mISmsStub).queryLocalInterface(anyString());
 
-        mWapPushOverSmsUT = new WapPushOverSms(mContext);
+        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        doReturn(MOCKED_MAIN_USER).when(userManager).getMainUser();
+
+        mWapPushOverSmsUT = new WapPushOverSms(mContext, mFeatureFlags);
     }
 
     @After
@@ -94,7 +108,7 @@
                 eq(AppOpsManager.OPSTR_RECEIVE_WAP_PUSH),
                 nullable(Bundle.class),
                 isNull(InboundSmsHandler.SmsBroadcastReceiver.class),
-                eq(UserHandle.SYSTEM),
+                eq(MOCKED_MAIN_USER),
                 anyInt());
         Intent intent = intentArgumentCaptor.getValue();
         assertEquals(Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION, intent.getAction());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
index 24f7d2c..cab76f7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaInboundSmsHandlerTest.java
@@ -83,6 +83,7 @@
     private InboundSmsTracker mInboundSmsTracker;
     private final byte[] mSmsPdu = new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF};
     private final int mSubId0 = 0;
+    private static final UserHandle MOCKED_MAIN_USER = UserHandle.of(10);
 
     private IState getCurrentState() {
         try {
@@ -108,10 +109,13 @@
 
         UserManager userManager = (UserManager) mContextFixture.getTestDouble().
                 getSystemService(Context.USER_SERVICE);
+        doReturn(MOCKED_MAIN_USER).when(userManager).getMainUser();
         doReturn(true).when(userManager).isUserUnlocked();
+        doReturn(true).when(mFeatureFlags).smsMmsDeliverBroadcastsRedirectToMainUser();
 
         try {
-            doReturn(new int[]{UserHandle.USER_SYSTEM}).when(mIActivityManager).getRunningUserIds();
+            doReturn(new int[]{0, MOCKED_MAIN_USER.getIdentifier()})
+                .when(mIActivityManager).getRunningUserIds();
         } catch (RemoteException re) {
             StringWriter reString = new StringWriter();
             re.printStackTrace(new PrintWriter(reString));
@@ -157,7 +161,8 @@
                 Telephony.Sms.CONTENT_URI.getAuthority(), mContentProvider);
 
         mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(mContext,
-            mSmsStorageMonitor, mPhone, null, mTestableLooper.getLooper());
+            mSmsStorageMonitor, mPhone, null, mTestableLooper.getLooper(),
+                mFeatureFlags);
         monitorTestableLooper(new TestableLooper(mCdmaInboundSmsHandler.getHandler().getLooper()));
         processAllMessages();
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsDispatcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsDispatcherTest.java
index 1f52cea..118adbc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsDispatcherTest.java
@@ -18,6 +18,7 @@
 
 import static org.mockito.Mockito.*;
 
+import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.Message;
 
@@ -38,6 +39,7 @@
 
     private CdmaSMSDispatcher mCdmaSmsDispatcher;
     private CdmaSmsDispatcherTestHandler mCdmaSmsDispatcherTestHandler;
+    private int mCallingUserId;
 
     private class CdmaSmsDispatcherTestHandler extends HandlerThread {
 
@@ -63,6 +65,7 @@
         mCdmaSmsDispatcherTestHandler = new CdmaSmsDispatcherTestHandler(TAG);
         mCdmaSmsDispatcherTestHandler.start();
         waitUntilReady();
+        mCallingUserId = Binder.getCallingUserHandle().getIdentifier();
     }
 
     @After
@@ -84,14 +87,14 @@
     @Test @SmallTest
     public void testSendText() {
         mCdmaSmsDispatcher.sendText("111"/* desAddr*/, "222" /*scAddr*/, TAG,
-                null, null, null, null, false, -1, false, -1, false, 0L);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
         verify(mSimulatedCommandsVerifier).sendCdmaSms(any(byte[].class), any(Message.class));
     }
 
     @Test @SmallTest
     public void testSendTextWithOutDesAddr() {
         mCdmaSmsDispatcher.sendText(null, "222" /*scAddr*/, TAG,
-                null, null, null, null, false, -1, false, -1, false, 0L);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
         verify(mSimulatedCommandsVerifier, times(0)).sendImsGsmSms(anyString(), anyString(),
                 anyInt(), anyInt(), any(Message.class));
     }
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 005b312..95cefd8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataConfigManagerTest.java
@@ -146,7 +146,6 @@
 
     @Test
     public void testMeteredNetworkCapabilities() {
-        doReturn(true).when(mFeatureFlags).meteredEmbbUrlcc();
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
                 new String[] {ApnSetting.TYPE_MMS_STRING, ApnSetting.TYPE_DEFAULT_STRING});
         mBundle.putStringArray(CarrierConfigManager.KEY_CARRIER_METERED_ROAMING_APN_TYPES_STRINGS,
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 499c1f5..ee713c6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -159,7 +159,6 @@
     private static final int EVENT_SUBSCRIPTION_OVERRIDE = 23;
 
     // Mocked classes
-    private PhoneSwitcher mMockedPhoneSwitcher;
     protected ISub mMockedIsub;
     private DataNetworkControllerCallback mMockedDataNetworkControllerCallback;
     private DataRetryManagerCallback mMockedDataRetryManagerCallback;
@@ -855,7 +854,6 @@
     public void setUp() throws Exception {
         logd("DataNetworkControllerTest +Setup!");
         super.setUp(getClass().getSimpleName());
-        mMockedPhoneSwitcher = Mockito.mock(PhoneSwitcher.class);
         mMockedIsub = Mockito.mock(ISub.class);
         mMockedImsManager = mContext.getSystemService(ImsManager.class);
         mMockedImsMmTelManager = Mockito.mock(ImsMmTelManager.class);
@@ -878,7 +876,6 @@
         mMockedDataServiceManagers.put(AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
                 mMockedWlanDataServiceManager);
 
-        replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, mMockedPhoneSwitcher);
         doReturn(1).when(mMockedIsub).getDefaultDataSubId();
         doReturn(mMockedIsub).when(mIBinder).queryLocalInterface(anyString());
         doReturn(mPhone).when(mPhone).getImsPhone();
@@ -891,10 +888,11 @@
         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)
-                .ignoreExistingNetworksForInternetAllowedChecking();
+        doReturn(true).when(mFeatureFlags).simDisabledGracefulTearDown();
+
         when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
         doReturn(true).when(mMockPackageManager).hasSystemFeature(anyString());
 
@@ -2844,7 +2842,6 @@
 
     @Test
     public void testHandoverDataNetworkNotAllowedByPolicyDelayDueToVoiceCall() throws Exception {
-        doReturn(true).when(mFeatureFlags).relaxHoTeardown();
         // Config delay IMS tear down enabled
         mCarrierConfig.putBoolean(CarrierConfigManager.KEY_DELAY_IMS_TEAR_DOWN_UNTIL_CALL_END_BOOL,
                 true);
@@ -3222,6 +3219,118 @@
     }
 
     @Test
+    public void testSetupDataNetworkWithCandidateProfileWithIncompatibleRetryDataProfile() throws Exception {
+        mDataNetworkControllerUT
+                .getDataRetryManager()
+                .registerCallback(mMockedDataRetryManagerCallback);
+        setFailedSetupDataResponse(mMockedWwanDataServiceManager,
+                DataFailCause.ONLY_IPV4_ALLOWED, 2500 /* mSec */, false);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        processAllMessages();
+        verify(mMockedWwanDataServiceManager, times(1)).setupDataCall(anyInt(),
+                any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
+                any(), any(), anyBoolean(), any(Message.class));
+
+        moveTimeForward(2500);
+        processAllMessages();
+        ArgumentCaptor<DataRetryManager.DataSetupRetryEntry> retryEntry =
+                ArgumentCaptor.forClass(DataRetryManager.DataSetupRetryEntry.class);
+        verify(mMockedDataRetryManagerCallback, times(1))
+                .onDataNetworkSetupRetry(retryEntry.capture());
+
+        ArgumentCaptor<List<ThrottleStatus>> throttleStatusCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mMockedDataRetryManagerCallback)
+                .onThrottleStatusChanged(throttleStatusCaptor.capture());
+
+        assertThat(retryEntry.getValue().dataProfile).isNotNull();
+
+        assertThat(retryEntry.getValue().dataProfile).isEqualTo(mGeneralPurposeDataProfile);
+        doReturn(false)
+                .when(mDataProfileManager)
+                .isDataProfileCompatible(retryEntry.getValue().dataProfile);
+
+        doReturn(mDuplicatedGeneralPurposeDataProfile).when(mDataProfileManager)
+                .getDataProfileForNetworkRequest(any(TelephonyNetworkRequest.class),
+                anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
+
+        setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 2);
+
+        mDataNetworkControllerUT
+                .getDataRetryManager()
+                .obtainMessage(
+                        6 /* EVENT_DATA_PROFILE_UNTHROTTLED*/,
+                        new AsyncResult(
+                                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                                mGeneralPurposeDataProfile,
+                                null))
+                .sendToTarget();
+        processAllFutureMessages();
+
+        verify(mMockedWwanDataServiceManager, times(2)).setupDataCall(anyInt(),
+                any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
+                any(), any(), anyBoolean(), any(Message.class));
+
+        verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        verifyConnectedNetworkHasDataProfile(mDuplicatedGeneralPurposeDataProfile);
+    }
+
+    @Test
+    public void testSetupDataNetworkRetryWithCompatibleRetryDataProfile() throws Exception {
+        mDataNetworkControllerUT
+                .getDataRetryManager()
+                .registerCallback(mMockedDataRetryManagerCallback);
+        setFailedSetupDataResponse(mMockedWwanDataServiceManager,
+                DataFailCause.ONLY_IPV4_ALLOWED, 2500 /* mSec */, false);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        processAllMessages();
+        verify(mMockedWwanDataServiceManager, times(1)).setupDataCall(anyInt(),
+                any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
+                any(), any(), anyBoolean(), any(Message.class));
+
+        moveTimeForward(2500);
+        processAllMessages();
+        ArgumentCaptor<DataRetryManager.DataSetupRetryEntry> retryEntry =
+                ArgumentCaptor.forClass(DataRetryManager.DataSetupRetryEntry.class);
+        verify(mMockedDataRetryManagerCallback, times(1))
+                .onDataNetworkSetupRetry(retryEntry.capture());
+
+        ArgumentCaptor<List<ThrottleStatus>> throttleStatusCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mMockedDataRetryManagerCallback)
+                .onThrottleStatusChanged(throttleStatusCaptor.capture());
+
+        assertThat(retryEntry.getValue().dataProfile).isNotNull();
+
+        assertThat(retryEntry.getValue().dataProfile).isEqualTo(mGeneralPurposeDataProfile);
+
+        assertThat(mDataProfileManager.isDataProfileCompatible(retryEntry.getValue().dataProfile))
+                .isTrue();
+
+        setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 2);
+
+        mDataNetworkControllerUT
+                .getDataRetryManager()
+                .obtainMessage(
+                        6 /* EVENT_DATA_PROFILE_UNTHROTTLED*/,
+                        new AsyncResult(
+                                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                                mGeneralPurposeDataProfile,
+                                null))
+                .sendToTarget();
+        processAllFutureMessages();
+
+        verify(mMockedWwanDataServiceManager, times(2)).setupDataCall(anyInt(),
+                any(DataProfile.class), anyBoolean(), anyBoolean(), anyInt(), any(), anyInt(),
+                any(), any(), anyBoolean(), any(Message.class));
+
+        verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        verifyConnectedNetworkHasDataProfile(mGeneralPurposeDataProfile);
+    }
+
+    @Test
     public void testSetupDataNetworkRetryFailed() {
         mDataNetworkControllerUT.getDataRetryManager()
                 .registerCallback(mMockedDataRetryManagerCallback);
@@ -3901,7 +4010,6 @@
 
     @Test
     public void testNonVoPStoVoPSImsSetup() throws Exception {
-        doReturn(true).when(mFeatureFlags).allowMmtelInNonVops();
         mDataNetworkControllerUT.getDataSettingsManager().setDataRoamingEnabled(true);
         // Config that allows non-vops bring up when Roaming
         mCarrierConfig.putIntArray(CarrierConfigManager.Ims
@@ -4203,7 +4311,7 @@
     }
 
     @Test
-    public void testImsGracefulTearDown() throws Exception {
+    public void testImsGracefulTearDownSimRemoval() throws Exception {
         setImsRegistered(true);
         setRcsRegistered(true);
 
@@ -4249,6 +4357,52 @@
     }
 
     @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);
 
@@ -4908,14 +5062,10 @@
 
         NetworkRequest nativeNetworkRequest = new NetworkRequest(netCaps,
                 ConnectivityManager.TYPE_MOBILE, 0, NetworkRequest.Type.REQUEST);
-
-        mDataNetworkControllerUT.addNetworkRequest(new TelephonyNetworkRequest(
-                nativeNetworkRequest, mPhone, mFeatureFlags));
-        processAllMessages();
-
-        // Intentionally create a new telephony request with the original native network request.
         TelephonyNetworkRequest request = new TelephonyNetworkRequest(
                 nativeNetworkRequest, mPhone, mFeatureFlags);
+        mDataNetworkControllerUT.addNetworkRequest(request);
+        processAllMessages();
 
         mDataNetworkControllerUT.removeNetworkRequest(request);
         processAllFutureMessages();
@@ -4936,7 +5086,7 @@
         processAllMessages();
 
         // this slot is 0, modem preferred on slot 1
-        doReturn(1).when(mMockedPhoneSwitcher).getPreferredDataPhoneId();
+        doReturn(1).when(mPhoneSwitcher).getPreferredDataPhoneId();
 
         // Simulate telephony network factory remove request due to switch.
         mDataNetworkControllerUT.removeNetworkRequest(request);
@@ -4949,7 +5099,7 @@
     @Test
     public void testSetupDataOnNonDds() throws Exception {
         // this slot is 0, modem preferred on slot 1
-        doReturn(1).when(mMockedPhoneSwitcher).getPreferredDataPhoneId();
+        doReturn(1).when(mPhoneSwitcher).getPreferredDataPhoneId();
         TelephonyNetworkRequest request = createNetworkRequest(
                 NetworkCapabilities.NET_CAPABILITY_MMS);
 
@@ -5475,4 +5625,18 @@
         assertThat(mDataNetworkControllerUT.getInternetEvaluation(true/*ignoreExistingNetworks*/)
                 .containsDisallowedReasons()).isTrue();
     }
+
+    @Test
+    public void testRemoveNetworkRequestClearState() throws Exception {
+        TelephonyNetworkRequest request = createNetworkRequest(
+                NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        mDataNetworkControllerUT.addNetworkRequest(request);
+        processAllMessages();
+        verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        assertThat(request.getState()).isEqualTo(TelephonyNetworkRequest.REQUEST_STATE_SATISFIED);
+
+        mDataNetworkControllerUT.removeNetworkRequest(request);
+        processAllMessages();
+        assertThat(request.getState()).isEqualTo(TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
index 09cfa6f..f832a6b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -86,6 +87,7 @@
 import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.data.LinkBandwidthEstimator.LinkBandwidthEstimatorCallback;
+import com.android.internal.telephony.data.PhoneSwitcher.PhoneSwitcherCallback;
 import com.android.internal.telephony.metrics.DataCallSessionStats;
 import com.android.internal.telephony.test.SimulatedCommands;
 
@@ -238,14 +240,14 @@
     // Mocked classes
     private DataNetworkCallback mDataNetworkCallback;
     private DataCallSessionStats mDataCallSessionStats;
-    private PhoneSwitcher mMockedPhoneSwitcher;
-
     private final NetworkRegistrationInfo mIwlanNetworkRegistrationInfo =
             new NetworkRegistrationInfo.Builder()
                     .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
                     .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                     .build();
 
+    private PhoneSwitcherCallback mPhoneSwitcherCallback;
+
     private void setSuccessfulSetupDataResponse(DataServiceManager dsm, int cid) {
         setSuccessfulSetupDataResponse(dsm, cid, Collections.emptyList(), null);
     }
@@ -379,14 +381,13 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
+        doReturn(1).when(mPhone).getSubId();
         doReturn(mImsPhone).when(mPhone).getImsPhone();
         doReturn(mImsCT).when(mImsPhone).getCallTracker();
         doReturn(PhoneConstants.State.IDLE).when(mImsCT).getState();
 
         mDataNetworkCallback = Mockito.mock(DataNetworkCallback.class);
         mDataCallSessionStats = Mockito.mock(DataCallSessionStats.class);
-        mMockedPhoneSwitcher = Mockito.mock(PhoneSwitcher.class);
-        replaceInstance(PhoneSwitcher.class, "sPhoneSwitcher", null, mMockedPhoneSwitcher);
         doAnswer(invocation -> {
             ((Runnable) invocation.getArguments()[0]).run();
             return null;
@@ -2418,10 +2419,12 @@
 
     @Test
     public void testMmsCapabilityRemovedWhenMmsPreferredOnIwlan() throws Exception {
-        doReturn(true).when(mFeatureFlags).forceIwlanMms();
-        doReturn(true).when(mDataConfigManager).isForceIwlanMmsFeatureEnabled();
         setupDataNetwork();
 
+        TelephonyNetworkAgent mockNetworkAgent = Mockito.mock(TelephonyNetworkAgent.class);
+        replaceInstance(DataNetwork.class, "mNetworkAgent",
+                mDataNetworkUT, mockNetworkAgent);
+
         assertThat(mDataNetworkUT.getNetworkCapabilities()
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)).isTrue();
 
@@ -2466,11 +2469,13 @@
                 .onPreferredTransportChanged(NetworkCapabilities.NET_CAPABILITY_MMS, false);
         processAllMessages();
 
-        // Check if MMS capability is removed.
+        // Check if MMS capability is removed, and we don't recreat network agent which triggers
+        // powering comsuming internet validation.
         assertThat(mDataNetworkUT.getNetworkCapabilities()
                 .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)).isFalse();
+        verify(mockNetworkAgent, never()).abandon();
 
-        // Now QNS prefers MMS on IWLAN
+        // Now QNS prefers MMS on WWAN
         doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).when(mAccessNetworksManager)
             .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
         accessNetworksManagerCallbackArgumentCaptor.getValue()
@@ -2657,4 +2662,35 @@
         assertThat(mDataNetworkUT.getNetworkCapabilities()
                 .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).isTrue();
     }
+
+    @Test
+    public void testPrimaryTransport() throws Exception {
+        doReturn(0).when(mPhoneSwitcher).getPreferredDataPhoneId();
+        setupDataNetwork();
+        TelephonyNetworkAgent mockNetworkAgent = Mockito.mock(TelephonyNetworkAgent.class);
+        replaceInstance(DataNetwork.class, "mNetworkAgent",
+                mDataNetworkUT, mockNetworkAgent);
+
+        ArgumentCaptor<PhoneSwitcherCallback> callbackCaptor =
+                ArgumentCaptor.forClass(PhoneSwitcherCallback.class);
+        verify(mPhoneSwitcher).registerCallback(callbackCaptor.capture());
+        mPhoneSwitcherCallback = callbackCaptor.getValue();
+
+        // Switch the preferred data subscription to another.
+        mPhoneSwitcherCallback.onPreferredDataPhoneIdChanged(1);
+        processAllMessages();
+
+        ArgumentCaptor<NetworkScore> networkScoreCaptor =
+                ArgumentCaptor.forClass(NetworkScore.class);
+        verify(mockNetworkAgent).sendNetworkScore(networkScoreCaptor.capture());
+        assertThat(networkScoreCaptor.getValue().isTransportPrimary()).isFalse();
+        clearInvocations(mockNetworkAgent);
+
+        // Switch back
+        mPhoneSwitcherCallback.onPreferredDataPhoneIdChanged(0);
+        processAllMessages();
+
+        verify(mockNetworkAgent).sendNetworkScore(networkScoreCaptor.capture());
+        assertThat(networkScoreCaptor.getValue().isTransportPrimary()).isTrue();
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
index 30ce46f..96d0c8c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
@@ -1046,7 +1046,6 @@
 
     @Test
     public void testSetPreferredDataProfile() {
-        doReturn(true).when(mFeatureFlags).refinePreferredDataProfileSelection();
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
index acfa16d..78bb553 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
@@ -341,7 +341,6 @@
 
     @Test
     public void testDataSetupUnthrottling() throws Exception {
-        doReturn(true).when(mFeatureFlags).unthrottleCheckTransport();
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
                 .build();
@@ -807,7 +806,6 @@
 
     @Test
     public void testDataRetryLongTimer() {
-        doReturn(true).when(mFeatureFlags).useAlarmCallback();
         // Rule requires a long timer
         DataSetupRetryRule retryRule = new DataSetupRetryRule(
                 "capabilities=internet, retry_interval=120000, maximum_retries=2");
@@ -882,6 +880,40 @@
     }
 
     @Test
+    public void testTrafficDescriptorRequestRetry() {
+        DataSetupRetryRule retryRule = new DataSetupRetryRule(
+                "capabilities=PRIORITIZE_BANDWIDTH, retry_interval=200, maximum_retries=2");
+        doReturn(Collections.singletonList(retryRule)).when(mDataConfigManager)
+                .getDataSetupRetryRules();
+        mDataConfigManagerCallback.onCarrierConfigChanged();
+        processAllMessages();
+
+        NetworkRequest request = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
+        DataNetworkController.NetworkRequestList
+                networkRequestList = new DataNetworkController.NetworkRequestList(tnr);
+
+        // failed and retry.
+        mDataRetryManagerUT.evaluateDataSetupRetry(mDataProfile1,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN, networkRequestList, 123,
+                DataCallResponse.RETRY_DURATION_UNDEFINED);
+        processAllFutureMessages();
+
+        tnr = new TelephonyNetworkRequest(new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                .build(), mPhone, mFeatureFlags);
+        assertThat(mDataRetryManagerUT.isSimilarNetworkRequestRetryScheduled(tnr,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).isTrue();
+        assertThat(mDataRetryManagerUT.isSimilarNetworkRequestRetryScheduled(tnr,
+                AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).isFalse();
+    }
+
+
+    @Test
     public void testRilCrashedReset() {
         testDataSetupRetryNetworkSuggestedNeverRetry();
         Mockito.clearInvocations(mDataRetryManagerCallbackMock, mDataProfileManager);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
index 3f18a3a..620cf39 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataSettingsManagerTest.java
@@ -42,6 +42,7 @@
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
 
 import org.junit.After;
 import org.junit.Before;
@@ -184,6 +185,49 @@
     }
 
     @Test
+    public void testUpdateDataEnabledAndNotifyOverrideDdsChange() throws Exception {
+        // Mock 2nd phone the DDS phone.
+        int phone2Id = 1;
+        int phone2SubId = 2;
+        doReturn(phone2SubId).when(mSubscriptionManagerService).getDefaultDataSubId();
+        Phone phone2 = Mockito.mock(Phone.class);
+        doReturn(phone2Id).when(phone2).getPhoneId();
+        doReturn(phone2SubId).when(phone2).getSubId();
+        doReturn(phone2Id).when(mSubscriptionManagerService).getPhoneId(phone2SubId);
+        DataSettingsManager dataSettingsManager2 = Mockito.mock(DataSettingsManager.class);
+        doReturn(dataSettingsManager2).when(phone2).getDataSettingsManager();
+        doReturn(true).when(phone2).isUserDataEnabled();
+
+        mPhones = new Phone[] {mPhone, phone2};
+        replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
+        ArgumentCaptor<SubscriptionManagerService.SubscriptionManagerServiceCallback>
+                callbackArgumentCaptor = ArgumentCaptor
+                .forClass(SubscriptionManagerService.SubscriptionManagerServiceCallback.class);
+
+        mDataSettingsManagerUT.sendEmptyMessage(11 /* EVENT_INITIALIZE */);
+        mDataSettingsManagerUT.setDataEnabled(TelephonyManager.DATA_ENABLED_REASON_USER, false, "");
+        processAllMessages();
+
+        // Verify listening to DDS change callback
+        verify(mSubscriptionManagerService, times(2))
+                .registerCallback(callbackArgumentCaptor.capture());
+        SubscriptionManagerService.SubscriptionManagerServiceCallback callback =
+                callbackArgumentCaptor.getValue();
+
+        // Mock the phone as nonDDS auto switch override enabled.
+        clearInvocations(mPhones);
+        mDataSettingsManagerUT.setMobileDataPolicy(
+                TelephonyManager.MOBILE_DATA_POLICY_AUTO_DATA_SWITCH, true);
+        processAllMessages();
+        verify(mPhone).notifyDataEnabled(true, TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
+
+        // The phone became DDS, data should be disabled
+        doReturn(mPhone.getSubId()).when(mSubscriptionManagerService).getDefaultDataSubId();
+        callback.onDefaultDataSubscriptionChanged(mPhone.getSubId());
+        verify(mPhone).notifyDataEnabled(false, TelephonyManager.DATA_ENABLED_REASON_OVERRIDE);
+    }
+
+    @Test
     public void testNotifyDataEnabledFromNewValidSubId() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         mDataSettingsManagerUT.registerCallback(
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 2a1fedb..f7990b9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
@@ -52,18 +52,15 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.content.Context;
 import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
-import android.net.NetworkProvider;
 import android.net.NetworkRequest;
 import android.net.TelephonyNetworkSpecifier;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Messenger;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhoneCapability;
@@ -75,8 +72,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
-import androidx.test.filters.SmallTest;
-
 import com.android.ims.ImsException;
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CommandException;
@@ -87,11 +82,11 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
-import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.imsphone.ImsPhoneCall;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService.SubscriptionManagerServiceCallback;
 
 import org.junit.After;
 import org.junit.Before;
@@ -140,9 +135,6 @@
 
     private PhoneSwitcher mPhoneSwitcherUT;
     private SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener;
-    private ConnectivityManager mConnectivityManager;
-    // The messenger of PhoneSwitcher used to receive network requests.
-    private Messenger mNetworkProviderMessenger = null;
     private Map<Integer, DataSettingsManager.DataSettingsManagerCallback>
             mDataSettingsManagerCallbacks;
     private int mDefaultDataSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -155,6 +147,7 @@
     private TelephonyDisplayInfo mTelephonyDisplayInfo = new TelephonyDisplayInfo(
             TelephonyManager.NETWORK_TYPE_NR,
             TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
+    private SubscriptionManagerServiceCallback mSubscriptionManagerServiceCallback;
 
     @Before
     public void setUp() throws Exception {
@@ -203,14 +196,13 @@
         mServiceManagerMockedServices.put("isub", mIBinder);
 
         doReturn(mTelephonyDisplayInfo).when(mDisplayInfoController).getTelephonyDisplayInfo();
+        doReturn(true).when(mFeatureFlags).ddsCallback();
     }
 
     @After
     public void tearDown() throws Exception {
         mPhoneSwitcherUT = null;
         mSubChangedListener = null;
-        mConnectivityManager = null;
-        mNetworkProviderMessenger = null;
         mTelephonyDisplayInfo = null;
         super.tearDown();
     }
@@ -219,7 +211,6 @@
      * Test that a single phone case results in our phone being active and the RIL called
      */
     @Test
-    @SmallTest
     public void testRegister() throws Exception {
         initialize();
 
@@ -466,7 +457,6 @@
     }
 
     @Test
-    @SmallTest
     public void testAutoDataSwitch_exemptPingTest() throws Exception {
         initialize();
 
@@ -504,7 +494,6 @@
      * - don't switch phones when in emergency mode
      */
     @Test
-    @SmallTest
     public void testPrioritization() throws Exception {
         initialize();
 
@@ -539,7 +528,6 @@
      * wins (ie, switch to wifi).
      */
     @Test
-    @SmallTest
     public void testHigherPriorityDefault() throws Exception {
         initialize();
 
@@ -572,7 +560,6 @@
      * active one.
      */
     @Test
-    @SmallTest
     public void testSetPreferredData() throws Exception {
         initialize();
 
@@ -618,7 +605,6 @@
      * 3. CBRS requests OR Auto switch requests - only one case applies at a time
      */
     @Test
-    @SmallTest
     public void testSetPreferredDataCasePriority_CbrsWaitsForVoiceCall() throws Exception {
         initialize();
         setAllPhonesInactive();
@@ -672,7 +658,6 @@
     }
 
     @Test
-    @SmallTest
     public void testSetPreferredData_NoAutoSwitchWhenCbrs() throws Exception {
         initialize();
         setAllPhonesInactive();
@@ -726,7 +711,6 @@
     }
 
     @Test
-    @SmallTest
     public void testSetPreferredDataModemCommand() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -825,7 +809,6 @@
     }
 
     @Test
-    @SmallTest
     public void testSetPreferredDataWithValidation() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -886,7 +869,6 @@
     }
 
     @Test
-    @SmallTest
     public void testNonDefaultDataPhoneInCall_ImsCallOnLte_shouldSwitchDds() throws Exception {
         initialize();
         setAllPhonesInactive();
@@ -914,7 +896,6 @@
     }
 
     @Test
-    @SmallTest
     public void testNonDefaultDataPhoneInCall_ImsCallDialingOnLte_shouldSwitchDds()
             throws Exception {
         initialize();
@@ -948,7 +929,6 @@
         assertEquals(1, mPhoneSwitcherUT.getPreferredDataPhoneId());
     }
     @Test
-    @SmallTest
     public void testNonDefaultDataPhoneInCall_ImsCallIncomingOnLte_shouldSwitchDds()
             throws Exception {
         initialize();
@@ -977,7 +957,6 @@
     }
 
     @Test
-    @SmallTest
     public void testNonDefaultDataPhoneInCall_ImsCallOnWlan_shouldNotSwitchDds() throws Exception {
         initialize();
         setAllPhonesInactive();
@@ -1004,7 +983,6 @@
     }
 
     @Test
-    @SmallTest
     public void testNonDefaultDataPhoneInCall_ImsCallOnCrossSIM_HandoverToLTE() throws Exception {
         initialize();
         setAllPhonesInactive();
@@ -1167,7 +1145,6 @@
     }
 
     @Test
-    @SmallTest
     public void testNetworkRequestOnNonDefaultData() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -1192,7 +1169,6 @@
     }
 
     @Test
-    @SmallTest
     public void testEmergencyOverrideSuccessBeforeCallStarts() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -1213,7 +1189,6 @@
     }
 
     @Test
-    @SmallTest
     public void testEmergencyOverrideNoDdsChange() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -1233,7 +1208,6 @@
     }
 
     @Test
-    @SmallTest
     public void testEmergencyOverrideEndSuccess() throws Exception {
         PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -1271,7 +1245,6 @@
     }
 
     @Test
-    @SmallTest
     public void testEmergencyOverrideEcbmStartEnd() throws Exception {
         PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -1321,7 +1294,6 @@
     }
 
     @Test
-    @SmallTest
     public void testEmergencyOverrideNoCallStart() throws Exception {
         PhoneSwitcher.DEFAULT_DATA_OVERRIDE_TIMEOUT_MS = 500;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -1353,7 +1325,6 @@
     }
 
     @Test
-    @SmallTest
     public void testEmergencyOverrideMultipleOverrideRequests() throws Exception {
         PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -1404,7 +1375,6 @@
     }
 
     @Test
-    @SmallTest
     public void testSetPreferredDataCallback() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -1579,7 +1549,6 @@
     }
 
     @Test
-    @SmallTest
     public void testMultiSimConfigChange() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         mActiveModemCount = 1;
@@ -1607,7 +1576,6 @@
     }
 
     @Test
-    @SmallTest
     public void testValidationOffSwitch_shouldSwitchOnNetworkAvailable() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -1648,7 +1616,6 @@
     }
 
     @Test
-    @SmallTest
     public void testValidationOffSwitch_shouldSwitchOnTimeOut() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
         initialize();
@@ -1771,7 +1738,6 @@
     }
 
     @Test
-    @SmallTest
     public void testRegisterForImsRegistrationCallback() throws Exception {
         initialize();
         setAllPhonesInactive();
@@ -1799,7 +1765,6 @@
     }
 
     @Test
-    @SmallTest
     public void testReceivingImsRegistrationTech() throws Exception {
         doReturn(true).when(mFeatureFlags).changeMethodOfObtainingImsRegistrationRadioTech();
 
@@ -1880,9 +1845,6 @@
         doReturn(true).when(mPhone2).isDataAllowed();
         doReturn(true).when(mDataSettingsManager2).isDataEnabled();
 
-        // 3.1 No default network
-        doReturn(null).when(mConnectivityManager).getNetworkCapabilities(any());
-
         mPhoneSwitcherUT.sendEmptyMessage(EVENT_EVALUATE_AUTO_SWITCH);
     }
 
@@ -2032,19 +1994,12 @@
         }
     }
 
-    private void sendDefaultDataSubChanged() {
-        final Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
-        mContext.sendBroadcast(intent);
-        processAllMessages();
-    }
-
     private void initialize() throws Exception {
         setNumPhones(mActiveModemCount, mSupportedModemCount);
 
         initializeSubControllerMock();
         initializeCommandInterfacesMock();
         initializeTelRegistryMock();
-        initializeConnManagerMock();
         initializeConfigMock();
 
         mPhoneSwitcherUT = new PhoneSwitcher(mMaxDataAttachModemCount, mContext, Looper.myLooper(),
@@ -2066,6 +2021,11 @@
         processAllMessages();
 
         verify(mTelephonyRegistryManager).addOnSubscriptionsChangedListener(any(), any());
+
+        ArgumentCaptor<SubscriptionManagerServiceCallback> callbackCaptor =
+                ArgumentCaptor.forClass(SubscriptionManagerServiceCallback.class);
+        verify(mSubscriptionManagerService).registerCallback(callbackCaptor.capture());
+        mSubscriptionManagerServiceCallback = callbackCaptor.getValue();
     }
 
     /**
@@ -2153,21 +2113,6 @@
      * Capture mNetworkProviderMessenger so that testing can request or release
      * network requests on PhoneSwitcher.
      */
-    private void initializeConnManagerMock() {
-        mConnectivityManager = (ConnectivityManager)
-                mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
-
-        doAnswer(invocation -> {
-            mNetworkProviderMessenger =
-                    ((NetworkProvider) invocation.getArgument(0)).getMessenger();
-            return null;
-        }).when(mConnectivityManager).registerNetworkProvider(any());
-    }
-
-    /**
-     * Capture mNetworkProviderMessenger so that testing can request or release
-     * network requests on PhoneSwitcher.
-     */
     private void initializeSubControllerMock() throws Exception {
         doReturn(mDefaultDataSub).when(mSubscriptionManagerService).getDefaultDataSubId();
         doReturn(mDefaultDataSub).when(mMockedIsub).getDefaultDataSubId();
@@ -2231,7 +2176,8 @@
             doAnswer(invocation -> phone.getSubId() == mDefaultDataSub)
                     .when(phone).isUserDataEnabled();
         }
-        sendDefaultDataSubChanged();
+        mSubscriptionManagerServiceCallback.onDefaultDataSubscriptionChanged(defaultDataSub);
+        processAllMessages();
     }
 
     private void setSlotIndexToSubId(int slotId, int subId) {
@@ -2263,13 +2209,7 @@
         NetworkRequest networkRequest = new NetworkRequest(netCap, ConnectivityManager.TYPE_NONE,
                 0, NetworkRequest.Type.REQUEST);
 
-        Message message = Message.obtain();
-        message.what = android.net.NetworkProvider.CMD_REQUEST_NETWORK;
-        message.arg1 = score;
-        message.obj = networkRequest;
-        mNetworkProviderMessenger.send(message);
-        processAllMessages();
-
+        mPhoneSwitcherUT.onRequestNetwork(networkRequest);
         return networkRequest;
     }
 
@@ -2287,14 +2227,7 @@
         }
         NetworkRequest networkRequest = new NetworkRequest(netCap, ConnectivityManager.TYPE_NONE,
                 1, NetworkRequest.Type.REQUEST);
-
-        Message message = Message.obtain();
-        message.what = android.net.NetworkProvider.CMD_REQUEST_NETWORK;
-        message.arg1 = 50; // Score
-        message.obj = networkRequest;
-        mNetworkProviderMessenger.send(message);
-        processAllMessages();
-
+        mPhoneSwitcherUT.onRequestNetwork(networkRequest);
         return networkRequest;
     }
 
@@ -2302,10 +2235,6 @@
      * Tell PhoneSwitcher to release a network request.
      */
     private void releaseNetworkRequest(NetworkRequest networkRequest) throws Exception {
-        Message message = Message.obtain();
-        message.what = android.net.NetworkProvider.CMD_CANCEL_REQUEST;
-        message.obj = networkRequest;
-        mNetworkProviderMessenger.send(message);
-        processAllMessages();
+        mPhoneSwitcherUT.onReleaseNetwork(networkRequest);
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkProviderTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkProviderTest.java
new file mode 100644
index 0000000..d62ee2f
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkProviderTest.java
@@ -0,0 +1,394 @@
+/*
+ * 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.data;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.annotation.NonNull;
+import android.net.MatchAllNetworkSpecifier;
+import android.net.NetworkCapabilities;
+import android.net.NetworkProvider;
+import android.net.NetworkRequest;
+import android.net.NetworkScore;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.connectivity.android.net.INetworkOfferCallback;
+import android.os.Looper;
+import android.telephony.Annotation.NetCapability;
+import android.telephony.SubscriptionManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.data.PhoneSwitcher.PhoneSwitcherCallback;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Arrays;
+import java.util.Set;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class TelephonyNetworkProviderTest extends TelephonyTest {
+
+    private TelephonyNetworkProvider mTelephonyNetworkProvider;
+
+    private PhoneSwitcherCallback mPhoneSwitcherCallback;
+
+    // Mocked classes
+    private DataNetworkController mDataNetworkController2;
+
+
+    /**
+     * Set the preferred data phone, which is supposed to take the network request.
+     *
+     * @param phoneId The phone id
+     */
+    private void setPreferredDataPhone(int phoneId) {
+        doAnswer(invocation -> {
+            TelephonyNetworkRequest request = (TelephonyNetworkRequest)
+                    invocation.getArguments()[0];
+            int id = (int) invocation.getArguments()[1];
+
+            logd("shouldApplyNetworkRequest: request phone id=" + id
+                    + ", preferred data phone id=" + phoneId);
+
+            TelephonyNetworkSpecifier specifier = (TelephonyNetworkSpecifier)
+                    request.getNetworkSpecifier();
+            if (specifier != null) {
+                int subId = specifier.getSubscriptionId();
+                logd("shouldApplyNetworkRequest: requested on sub " + subId);
+                if (subId == 1 && mPhone.getPhoneId() == id) {
+                    logd("shouldApplyNetworkRequest: matched phone 0");
+                    return true;
+                }
+                if (subId == 2 && mPhone2.getPhoneId() == id) {
+                    logd("shouldApplyNetworkRequest: matched phone 1");
+                    return true;
+                }
+                return false;
+            }
+
+            if (request.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) return true;
+            return id == phoneId;
+        }).when(mPhoneSwitcher).shouldApplyNetworkRequest(any(TelephonyNetworkRequest.class),
+                anyInt());
+    }
+
+    /**
+     * Create a simple network request with internet capability.
+     *
+     * @return The network request
+     */
+    @NonNull
+    private NetworkRequest createNetworkRequest() {
+        return createNetworkRequestForSub(SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+                NetworkCapabilities.NET_CAPABILITY_INTERNET);
+    }
+
+    /**
+     * Create a network request with specified network capabilities.
+     *
+     * @param caps Network capabilities
+     *
+     * @return The network request
+     */
+    @NonNull
+    private NetworkRequest createNetworkRequest(@NetCapability int... caps) {
+        return createNetworkRequestForSub(SubscriptionManager.INVALID_SUBSCRIPTION_ID, caps);
+    }
+
+    /**
+     * Create a network request with subscription id specified.
+     *
+     * @param subId The subscription in for the network request
+     *
+     * @return The network request
+     */
+    @NonNull
+    private NetworkRequest createNetworkRequestForSub(int subId) {
+        return createNetworkRequestForSub(subId, NetworkCapabilities.NET_CAPABILITY_INTERNET);
+    }
+
+    /**
+     * Create the network request.
+     *
+     * @param subId The subscription id. {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if no
+     * @param caps Network capabilities in the network request need to specify.
+     *
+     * @return The network request
+     */
+    @NonNull
+    private NetworkRequest createNetworkRequestForSub(int subId, @NetCapability int... caps) {
+        NetworkRequest.Builder builder = new NetworkRequest.Builder();
+        Arrays.stream(caps).boxed().toList().forEach(builder::addCapability);
+        if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+            builder.setNetworkSpecifier(new TelephonyNetworkSpecifier(subId));
+            builder.setSubscriptionIds(Set.of(subId));
+        }
+
+        return builder.build();
+    }
+
+    /** Clear all invocations from all DataNetworkControllers. */
+    private void resetInvocations() {
+        clearInvocations(mDataNetworkController);
+        clearInvocations(mDataNetworkController2);
+    }
+
+    /**
+     * Verify the request was sent to the correct phone's DataNetworkController.
+     *
+     * @param phoneId The id of the phone that the request is supposed to send
+     * @param request The network request
+     */
+    private void verifyRequestSentOnPhone(int phoneId, @NonNull NetworkRequest request) {
+        ArgumentCaptor<TelephonyNetworkRequest> requestCaptor =
+                ArgumentCaptor.forClass(TelephonyNetworkRequest.class);
+
+        for (Phone phone : PhoneFactory.getPhones()) {
+            if (phone.getPhoneId() == phoneId) {
+                verify(phone.getDataNetworkController(), times(1)
+                        .description("Did not request on phone " + phoneId))
+                        .addNetworkRequest(requestCaptor.capture());
+                assertThat(requestCaptor.getValue().getNativeNetworkRequest()).isEqualTo(request);
+            } else {
+                verifyNoRequestSentOnPhone(phone.getPhoneId());
+            }
+        }
+    }
+
+    /**
+     * Verify the request was released on the specified phone's DataNetworkController.
+     *
+     * @param phoneId The id of the phone that the request is supposed to send
+     * @param request The network request
+     */
+    private void verifyRequestReleasedOnPhone(int phoneId, @NonNull NetworkRequest request) {
+        ArgumentCaptor<TelephonyNetworkRequest> requestCaptor =
+                ArgumentCaptor.forClass(TelephonyNetworkRequest.class);
+
+        for (Phone phone : PhoneFactory.getPhones()) {
+            if (phone.getPhoneId() == phoneId) {
+                verify(phone.getDataNetworkController(), times(1)
+                        .description("Did not remove on phone " + phoneId))
+                        .removeNetworkRequest(requestCaptor.capture());
+                assertThat(requestCaptor.getValue().getNativeNetworkRequest()).isEqualTo(request);
+            } else {
+                verifyNoRequestReleasedOnPhone(phone.getPhoneId());
+            }
+        }
+    }
+
+    /**
+     * Verify there is no request sent on specified phone.
+     *
+     * @param phoneId The phone id
+     */
+    private void verifyNoRequestSentOnPhone(int phoneId) {
+        verify(PhoneFactory.getPhone(phoneId).getDataNetworkController(), never()
+                .description("Should not request on phone " + phoneId))
+                .addNetworkRequest(any(TelephonyNetworkRequest.class));
+    }
+
+    /**
+     * Verify there is no request released on specified phone.
+     *
+     * @param phoneId The phone id
+     */
+    private void verifyNoRequestReleasedOnPhone(int phoneId) {
+        verify(PhoneFactory.getPhone(phoneId).getDataNetworkController(), never()
+                .description("Should not release on phone " + phoneId))
+                .removeNetworkRequest(any(TelephonyNetworkRequest.class));
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        logd("TelephonyNetworkProviderTest +Setup!");
+        super.setUp(getClass().getSimpleName());
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone, mPhone2});
+
+        mDataNetworkController2 = mock(DataNetworkController.class);
+
+        doReturn(0).when(mPhone).getPhoneId();
+        doReturn(1).when(mPhone).getSubId();
+        doReturn(1).when(mPhone2).getPhoneId();
+        doReturn(2).when(mPhone2).getSubId();
+
+        doReturn(mDataNetworkController2).when(mPhone2).getDataNetworkController();
+
+        setPreferredDataPhone(0);
+
+        doAnswer(invocation -> {
+            NetworkProvider provider = (NetworkProvider) invocation.getArguments()[0];
+            provider.setProviderId(1);
+            return 1;
+        }).when(mConnectivityManager).registerNetworkProvider(any(NetworkProvider.class));
+
+        mTelephonyNetworkProvider = new TelephonyNetworkProvider(Looper.myLooper(),
+                mContext, mFeatureFlags);
+
+        ArgumentCaptor<PhoneSwitcherCallback> callbackCaptor =
+                ArgumentCaptor.forClass(PhoneSwitcherCallback.class);
+        verify(mPhoneSwitcher).registerCallback(callbackCaptor.capture());
+        mPhoneSwitcherCallback = callbackCaptor.getValue();
+
+        logd("TelephonyNetworkProviderTest -Setup!");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        logd("tearDown");
+        super.tearDown();
+    }
+
+    @Test
+    public void testRegisterProvider() {
+        verify(mConnectivityManager).registerNetworkProvider(any(TelephonyNetworkProvider.class));
+
+        ArgumentCaptor<NetworkCapabilities> capsCaptor =
+                ArgumentCaptor.forClass(NetworkCapabilities.class);
+        verify(mConnectivityManager).offerNetwork(anyInt(), any(NetworkScore.class),
+                capsCaptor.capture(), any(INetworkOfferCallback.class));
+
+        NetworkCapabilities caps = capsCaptor.getValue();
+
+        TelephonyNetworkRequest.getAllSupportedNetworkCapabilities().forEach(
+                (cap) -> assertThat(caps.hasCapability(cap)).isTrue());
+        assertThat(caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_IA)).isTrue();
+        assertThat(caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL)).isTrue();
+        assertThat(caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
+        assertThat(caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)).isTrue();
+        assertThat(caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).isTrue();
+        assertThat(caps.hasTransport(NetworkCapabilities.TRANSPORT_SATELLITE)).isTrue();
+
+        assertThat(caps.getNetworkSpecifier()).isInstanceOf(MatchAllNetworkSpecifier.class);
+    }
+
+    @Test
+    public void testRequestNetwork() {
+        NetworkRequest request = createNetworkRequest();
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        // Should request on phone 0
+        verifyRequestSentOnPhone(0, request);
+    }
+
+    @Test
+    public void testReleaseNetwork() {
+        NetworkRequest request = createNetworkRequest();
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        // Should request on phone 0
+        verifyRequestSentOnPhone(0, request);
+        resetInvocations();
+
+        // Now release the network request.
+        mTelephonyNetworkProvider.onNetworkUnneeded(request);
+        // Should release on phone 0
+        verifyRequestReleasedOnPhone(0, request);
+        resetInvocations();
+
+        // Release the same request again should not result in another remove
+        mTelephonyNetworkProvider.onNetworkUnneeded(request);
+        verifyNoRequestReleasedOnPhone(0);
+        verifyNoRequestReleasedOnPhone(1);
+    }
+
+    @Test
+    public void testRequestNetworkDuplicate() {
+        NetworkRequest request = createNetworkRequest();
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        // Should request on phone 0
+        verifyRequestSentOnPhone(0, request);
+
+        resetInvocations();
+        // send the same request again should be blocked.
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        verifyNoRequestSentOnPhone(0);
+        verifyNoRequestSentOnPhone(1);
+    }
+
+    @Test
+    public void testRequestNetworkPreferredPhone1() {
+        setPreferredDataPhone(1);
+
+        NetworkRequest request = createNetworkRequest();
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        // Should request on phone 1
+        verifyRequestSentOnPhone(1, request);
+    }
+
+    @Test
+    public void testRequestEmergencyNetwork() {
+        setPreferredDataPhone(1);
+
+        NetworkRequest request = createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_EIMS);
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        // Should request on phone 0
+        verifyRequestSentOnPhone(0, request);
+    }
+
+    @Test
+    public void testRequestNetworkOnSpecifiedSub() {
+        NetworkRequest request = createNetworkRequestForSub(1);
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        verifyRequestSentOnPhone(0, request);
+
+        resetInvocations();
+        request = createNetworkRequestForSub(2);
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        // Should request on phone 1
+        verifyRequestSentOnPhone(1, request);
+    }
+
+    @Test
+    public void testPreferredDataSwitch() {
+        NetworkRequest request = createNetworkRequest();
+        mTelephonyNetworkProvider.onNetworkNeeded(request);
+        // Should request on phone 0
+        verifyRequestSentOnPhone(0, request);
+        resetInvocations();
+
+        // Now switch from phone 0 to phone 1
+        setPreferredDataPhone(1);
+        mPhoneSwitcherCallback.onPreferredDataPhoneIdChanged(1);
+        verifyRequestReleasedOnPhone(0, request);
+        verifyRequestSentOnPhone(1, request);
+        resetInvocations();
+
+        // Now switch back to phone 0
+        setPreferredDataPhone(0);
+        mPhoneSwitcherCallback.onPreferredDataPhoneIdChanged(0);
+        verifyRequestReleasedOnPhone(1, request);
+        verifyRequestSentOnPhone(0, request);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkRequestTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkRequestTest.java
index 9f11a3a..27c87e2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkRequestTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkRequestTest.java
@@ -189,7 +189,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)
                         .build(), mPhone, mFeatureFlags);
-        assertThat(request.getApnTypeNetworkCapability())
+        assertThat(request.getHighestPriorityApnTypeNetworkCapability())
                 .isEqualTo(NetworkCapabilities.NET_CAPABILITY_SUPL);
 
         request = new TelephonyNetworkRequest(
@@ -198,7 +198,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
                         .build(), mPhone, mFeatureFlags);
-        assertThat(request.getApnTypeNetworkCapability())
+        assertThat(request.getHighestPriorityApnTypeNetworkCapability())
                 .isEqualTo(NetworkCapabilities.NET_CAPABILITY_FOTA);
 
         request = new TelephonyNetworkRequest(
@@ -207,7 +207,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
                         .build(), mPhone, mFeatureFlags);
-        assertThat(request.getApnTypeNetworkCapability())
+        assertThat(request.getHighestPriorityApnTypeNetworkCapability())
                 .isEqualTo(NetworkCapabilities.NET_CAPABILITY_EIMS);
     }
 
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 5fd7a77..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,9 +16,12 @@
 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;
 import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
+import static android.telephony.DomainSelectionService.SELECTOR_TYPE_SMS;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -46,6 +49,7 @@
 import android.telephony.DisconnectCause;
 import android.telephony.DomainSelectionService;
 import android.telephony.EmergencyRegistrationResult;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PreciseDisconnectCause;
 import android.telephony.data.ApnSetting;
 import android.telephony.ims.ImsReasonInfo;
@@ -55,6 +59,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.CallFailCause;
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.IDomainSelector;
 import com.android.internal.telephony.ITransportSelectorCallback;
 import com.android.internal.telephony.ITransportSelectorResultCallback;
@@ -83,6 +89,7 @@
 
     private static final String TELECOM_CALL_ID1 = "TC1";
 
+    private CommandsInterface mCi;
     private DomainSelectionController mDomainSelectionController;
     private DomainSelectionConnection.DomainSelectionConnectionCallback mConnectionCallback;
     private DomainSelectionConnection mDsc;
@@ -91,6 +98,9 @@
     public void setUp() throws Exception {
         super.setUp(this.getClass().getSimpleName());
 
+        mCi = Mockito.mock(CommandsInterface.class);
+        mPhone.mCi = mCi;
+
         mDomainSelectionController = Mockito.mock(DomainSelectionController.class);
         doReturn(true).when(mDomainSelectionController).selectDomain(any(), any());
         mConnectionCallback =
@@ -187,6 +197,7 @@
 
         verify(mPhone).registerForEmergencyNetworkScan(
                 handlerCaptor.capture(), eventCaptor.capture(), any());
+        verify(mCi).registerForModemReset(any(), anyInt(), any());
 
         int[] expectedPreferredNetworks = new int[] { EUTRAN, UTRAN };
 
@@ -235,6 +246,7 @@
         processAllMessages();
 
         verify(mPhone).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mCi).registerForModemReset(any(), anyInt(), any());
         verify(mPhone).triggerEmergencyNetworkScan(any(), anyInt(), any());
 
         wwanCallback.onCancel();
@@ -280,6 +292,7 @@
         verify(mPhone).cancelEmergencyNetworkScan(eq(true), msgCaptor.capture());
 
         verify(mPhone, times(0)).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mCi, times(0)).registerForModemReset(any(), anyInt(), any());
         verify(mPhone, times(0)).triggerEmergencyNetworkScan(any(), anyInt(), any());
 
         Message msg = msgCaptor.getValue();
@@ -295,6 +308,7 @@
 
         verify(mPhone).registerForEmergencyNetworkScan(
                 handlerCaptor.capture(), eventCaptor.capture(), any());
+        verify(mCi).registerForModemReset(any(), anyInt(), any());
 
         int[] expectedPreferredNetworks = new int[] { EUTRAN, UTRAN };
 
@@ -350,6 +364,7 @@
 
         verify(mPhone).cancelEmergencyNetworkScan(eq(true), msgCaptor.capture());
         verify(mPhone, times(0)).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mCi, times(0)).registerForModemReset(any(), anyInt(), any());
         verify(mPhone, times(0)).triggerEmergencyNetworkScan(any(), anyInt(), any());
 
         Message msg = msgCaptor.getValue();
@@ -363,6 +378,7 @@
 
         // Verify that scan is requested.
         verify(mPhone).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mCi).registerForModemReset(any(), anyInt(), any());
         verify(mPhone).triggerEmergencyNetworkScan(any(), anyInt(), any());
 
         // Cancele scan after reset completes.
@@ -410,6 +426,7 @@
 
         verify(mPhone).cancelEmergencyNetworkScan(eq(true), msgCaptor.capture());
         verify(mPhone, times(0)).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mCi, times(0)).registerForModemReset(any(), anyInt(), any());
         verify(mPhone, times(0)).triggerEmergencyNetworkScan(any(), anyInt(), any());
 
         Message msg = msgCaptor.getValue();
@@ -430,11 +447,395 @@
 
         // Verify there is no scan request after reset completes.
         verify(mPhone, times(0)).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mCi, times(0)).registerForModemReset(any(), anyInt(), any());
         verify(mPhone, times(0)).triggerEmergencyNetworkScan(any(), anyInt(), any());
     }
 
     @Test
     @SmallTest
+    public void testWwanSelectorCallbackOnRequestEmergencyNetworkScanAndRadioNotAvailableError()
+            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.RADIO_NOT_AVAILABLE);
+        handler.sendMessage(handler.obtainMessage(msg.what,
+                new AsyncResult((Integer) scanType, null, commandException)));
+        processAllMessages();
+
+        verify(resultCallback, times(0)).onComplete(any());
+        verify(mPhone).unregisterForEmergencyNetworkScan(any());
+        verify(mCi).unregisterForModemReset(any());
+        verify(mPhone, times(0)).cancelEmergencyNetworkScan(anyBoolean(), any());
+        verify(mDomainSelectionController).removeConnection(eq(mDsc));
+    }
+
+    @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,
+                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<Integer> eventCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        verify(mPhone).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mCi).registerForModemReset(handlerCaptor.capture(), eventCaptor.capture(),
+                any());
+        processAllMessages();
+
+        int[] expectedPreferredNetworks = new int[] { EUTRAN, UTRAN };
+
+        verify(mPhone).triggerEmergencyNetworkScan(eq(expectedPreferredNetworks),
+                eq(scanType), any());
+
+        Handler handler = handlerCaptor.getValue();
+        Integer event = eventCaptor.getValue();
+
+        assertNotNull(handler);
+        assertNotNull(event);
+
+        handler.sendMessage(handler.obtainMessage(event.intValue(),
+                new AsyncResult(null, null, null)));
+        processAllMessages();
+
+        verify(resultCallback, times(0)).onComplete(any());
+        verify(mPhone).unregisterForEmergencyNetworkScan(any());
+        verify(mCi).unregisterForModemReset(any());
+        verify(mPhone).cancelEmergencyNetworkScan(anyBoolean(), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testModemResetOnRequestEmergencyNetworkDuringNetworkScanForSms()
+            throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_SMS, true,
+                mDomainSelectionController);
+
+        ITransportSelectorCallback transportCallback = mDsc.getTransportSelectorCallback();
+
+        assertNotNull(transportCallback);
+
+        DomainSelectionService.SelectionAttributes attr = getSelectionAttributes(
+                mPhone.getPhoneId(), mPhone.getSubId(), SELECTOR_TYPE_SMS, 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<Integer> eventCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        verify(mPhone).registerForEmergencyNetworkScan(any(), anyInt(), any());
+        verify(mCi).registerForModemReset(handlerCaptor.capture(), eventCaptor.capture(),
+                any());
+        processAllMessages();
+
+        int[] expectedPreferredNetworks = new int[] { EUTRAN, UTRAN };
+
+        verify(mPhone).triggerEmergencyNetworkScan(eq(expectedPreferredNetworks),
+                eq(scanType), any());
+
+        Handler handler = handlerCaptor.getValue();
+        Integer event = eventCaptor.getValue();
+
+        assertNotNull(handler);
+        assertNotNull(event);
+
+        handler.sendMessage(handler.obtainMessage(event.intValue(),
+                new AsyncResult(null, null, null)));
+        processAllMessages();
+
+        verify(mPhone, times(0)).unregisterForEmergencyNetworkScan(any());
+        verify(mCi, times(0)).unregisterForModemReset(any());
+        verify(mPhone, times(0)).cancelEmergencyNetworkScan(anyBoolean(), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testModemResetOnRequestEmergencyNetworkAfterNetworkScanDone()
+            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<Integer> scanEventCaptor = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> resetEventCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        verify(mPhone).registerForEmergencyNetworkScan(any(), scanEventCaptor.capture(), any());
+        verify(mCi).registerForModemReset(handlerCaptor.capture(),
+                resetEventCaptor.capture(), any());
+        processAllMessages();
+
+        int[] expectedPreferredNetworks = new int[] { EUTRAN, UTRAN };
+
+        verify(mPhone).triggerEmergencyNetworkScan(eq(expectedPreferredNetworks),
+                eq(scanType), any());
+
+        Handler handler = handlerCaptor.getValue();
+        Integer scanEvent = scanEventCaptor.getValue();
+        Integer resetEvent = resetEventCaptor.getValue();
+
+        assertNotNull(handler);
+        assertNotNull(scanEvent);
+        assertNotNull(resetEvent);
+
+        EmergencyRegistrationResult regResult =
+                new EmergencyRegistrationResult(UTRAN, 0, 0, true, false, 0, 0, "", "", "");
+        handler.sendMessage(handler.obtainMessage(scanEvent,
+                new AsyncResult(null, regResult, null)));
+        processAllMessages();
+        wwanCallback.onDomainSelected(NetworkRegistrationInfo.DOMAIN_CS_PS, true);
+
+        handler.sendMessage(handler.obtainMessage(resetEvent, new AsyncResult(null, null, null)));
+        processAllMessages();
+
+        verify(resultCallback).onComplete(any());
+        verify(mPhone, times(0)).unregisterForEmergencyNetworkScan(any());
+        verify(mCi, times(0)).unregisterForModemReset(any());
+        verify(mPhone, times(0)).cancelEmergencyNetworkScan(anyBoolean(), any());
+    }
+
+    @Test
+    @SmallTest
     public void testDomainSelectorCancelSelection() throws Exception {
         mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
                 mDomainSelectionController);
@@ -601,6 +1002,7 @@
 
         verify(mPhone).registerForEmergencyNetworkScan(
                 handlerCaptor.capture(), eventCaptor.capture(), any());
+        verify(mCi).registerForModemReset(any(), anyInt(), any());
 
         int[] expectedPreferredNetworks = new int[] { EUTRAN, UTRAN };
 
@@ -686,6 +1088,7 @@
 
         verify(mPhone).registerForEmergencyNetworkScan(
                 handlerCaptor.capture(), eventCaptor.capture(), any());
+        verify(mCi).registerForModemReset(any(), anyInt(), any());
 
         int[] expectedPreferredNetworks = new int[] { EUTRAN, UTRAN };
 
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 0eb5c13..62c2010 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
@@ -21,6 +21,8 @@
 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;
@@ -36,6 +38,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;
@@ -121,6 +124,7 @@
 
         doReturn(TelephonyManager.SIM_STATE_READY)
                 .when(mTelephonyManagerProxy).getSimState(anyInt());
+        doReturn(true).when(mFeatureFlags).emergencyCallbackModeNotification();
     }
 
     @After
@@ -135,7 +139,7 @@
             EmergencyStateTracker.getInstance();
         });
 
-        EmergencyStateTracker.make(mContext, true);
+        EmergencyStateTracker.make(mContext, true, mFeatureFlags);
 
         assertNotNull(EmergencyStateTracker.getInstance());
     }
@@ -143,7 +147,7 @@
     @Test
     @SmallTest
     public void getInstance_returnsSameInstance() {
-        EmergencyStateTracker.make(mContext, true);
+        EmergencyStateTracker.make(mContext, true, mFeatureFlags);
         EmergencyStateTracker instance1 = EmergencyStateTracker.getInstance();
         EmergencyStateTracker instance2 = EmergencyStateTracker.getInstance();
 
@@ -346,7 +350,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");
@@ -363,7 +367,7 @@
         // 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 +395,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);
@@ -761,6 +765,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 +825,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 +863,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 +1017,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 +1026,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 +1102,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, never()).stopEmergencyCallbackMode(
+                eq(EMERGENCY_CALLBACK_MODE_CALL), anyInt());
     }
 
     @Test
@@ -1224,6 +1243,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 +1276,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 +1309,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 +1436,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 +1497,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 +1752,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 +2183,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 +2240,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());
@@ -3379,7 +3409,7 @@
                 anyInt(), anyInt(), any());
         return new EmergencyStateTracker(mContext, mTestableLooper.getLooper(),
                 isSuplDdsSwitchRequiredForEmergencyCall, mPhoneFactoryProxy, mPhoneSwitcherProxy,
-                mTelephonyManagerProxy, mRadioOnHelper, TEST_ECM_EXIT_TIMEOUT_MS);
+                mTelephonyManagerProxy, mRadioOnHelper, TEST_ECM_EXIT_TIMEOUT_MS, mFeatureFlags);
     }
 
     private Phone setupTestPhoneForEmergencyCall(boolean isRoaming, boolean isRadioOn) {
@@ -3483,6 +3513,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 037f82c..e9bff91 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java
@@ -90,11 +90,10 @@
         waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
 
         verify(mMockPhone).unregisterForServiceStateChanged(any(Handler.class));
-        verify(mSatelliteController).unregisterForModemStateChanged(anyInt(), any());
+        verify(mSatelliteController).unregisterForModemStateChanged(any());
         verify(mMockPhone).registerForServiceStateChanged(any(Handler.class),
                 eq(RadioOnStateListener.MSG_SERVICE_STATE_CHANGED), isNull());
-        verify(mSatelliteController, never()).registerForSatelliteModemStateChanged(
-                anyInt(), any());
+        verify(mSatelliteController, never()).registerForSatelliteModemStateChanged(any());
 
         verify(mMockCi).registerForOffOrNotAvailable(any(Handler.class),
                 eq(RadioOnStateListener.MSG_RADIO_OFF_OR_NOT_AVAILABLE), isNull());
@@ -105,13 +104,13 @@
      */
     @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);
 
-        verify(mSatelliteController).unregisterForModemStateChanged(anyInt(), any());
-        verify(mSatelliteController).registerForSatelliteModemStateChanged(anyInt(), any());
+        verify(mSatelliteController).unregisterForModemStateChanged(any());
+        verify(mSatelliteController).registerForSatelliteModemStateChanged(any());
     }
 
     /**
@@ -205,7 +204,7 @@
         verify(mCallback).onComplete(eq(mListener), eq(false));
         verify(mMockPhone, times(2)).setRadioPower(eq(true), eq(false), eq(false), eq(false));
         verify(mSatelliteController, never()).requestSatelliteEnabled(
-                anyInt(), eq(false), eq(false), eq(false), any());
+                eq(false), eq(false), eq(false), any());
     }
 
     @Test
@@ -225,12 +224,12 @@
         verify(mCallback).onComplete(eq(mListener), eq(false));
         verify(mMockPhone, times(2)).setRadioPower(eq(true), eq(true), eq(true), eq(false));
         verify(mSatelliteController, never()).requestSatelliteEnabled(
-                anyInt(), eq(false), eq(false), eq(false), any());
+                eq(false), eq(false), eq(false), any());
     }
 
     @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);
@@ -246,7 +245,7 @@
         verify(mCallback).onComplete(eq(mListener), eq(false));
         verify(mMockPhone, times(2)).setRadioPower(eq(true), eq(true), eq(true), eq(false));
         verify(mSatelliteController, times(2)).requestSatelliteEnabled(
-                anyInt(), eq(false), eq(false), eq(false), any());
+                eq(false), eq(false), eq(false), any());
     }
 
     @Test
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 e374551..569785b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
@@ -41,7 +41,6 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
-import android.app.admin.flags.Flags;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.Context;
 import android.content.Intent;
@@ -49,6 +48,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.UserManager;
@@ -87,8 +87,13 @@
 import org.junit.Test;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.mockito.stubbing.Answer;
 import org.mockito.stubbing.Stubber;
 
@@ -106,6 +111,8 @@
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
 
     private static final DownloadableSubscription SUBSCRIPTION =
             DownloadableSubscription.forActivationCode("abcde");
@@ -145,8 +152,8 @@
     private static final long AVAILABLE_MEMORY = 123L;
 
     // Mocked classes
-    private EuiccConnector mMockConnector;
-    private UiccSlot mUiccSlot;
+    @Mock private EuiccConnector mMockConnector;
+    @Captor private ArgumentCaptor<Bundle> mBundleCaptor;
 
     private TestEuiccController mController;
     private int mSavedEuiccProvisionedValue;
@@ -218,8 +225,6 @@
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
-        mMockConnector = Mockito.mock(EuiccConnector.class);
-        mUiccSlot = Mockito.mock(UiccSlot.class);
         mController = new TestEuiccController(mContext, mMockConnector, mFeatureFlags);
 
         PackageInfo pi = new PackageInfo();
@@ -580,8 +585,15 @@
     public void testDownloadSubscription_success() throws Exception {
         setHasWriteEmbeddedPermission(true);
         setUpUiccSlotData();
+
         callDownloadSubscription(SUBSCRIPTION, true /* switchAfterDownload */, true /* complete */,
                 EuiccService.RESULT_OK, 0 /* resolvableError */, "whatever" /* callingPackage */);
+
+        verify(mMockConnector).downloadSubscription(anyInt(), anyInt(),
+                any(), eq(true), anyBoolean(), mBundleCaptor.capture(), any());
+        assertEquals(
+                "whatever",
+                mBundleCaptor.getValue().getString(EuiccService.EXTRA_PACKAGE_NAME));
         verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
         // switchAfterDownload = true so no refresh should occur.
         assertFalse(mController.mCalledRefreshSubscriptionsAndSendResult);
@@ -591,8 +603,15 @@
     @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
     public void testDownloadSubscription_noSwitch_success() throws Exception {
         setHasWriteEmbeddedPermission(true);
+
         callDownloadSubscription(SUBSCRIPTION, false /* switchAfterDownload */, true /* complete */,
                 EuiccService.RESULT_OK, 0 /* resolvableError */, "whatever" /* callingPackage */);
+
+        verify(mMockConnector).downloadSubscription(anyInt(), anyInt(),
+                any(), eq(false), anyBoolean(), mBundleCaptor.capture(), any());
+        assertEquals(
+                "whatever",
+                mBundleCaptor.getValue().getString(EuiccService.EXTRA_PACKAGE_NAME));
         verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
         assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult);
     }
@@ -857,7 +876,6 @@
     @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
     public void testDownloadSubscription_noAdminPermission()
             throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         setHasWriteEmbeddedPermission(false);
         setHasManageDevicePolicyManagedSubscriptionsPermission(false);
         setUpUiccSlotData();
@@ -883,7 +901,6 @@
     @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
     public void testDownloadSubscription_adminPermission()
             throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         setHasManageDevicePolicyManagedSubscriptionsPermission(true);
         setHasWriteEmbeddedPermission(false);
 
@@ -898,7 +915,6 @@
     @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
     public void testDownloadSubscription_adminPermission_usingSwitchAfterDownload_fails()
             throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         setHasWriteEmbeddedPermission(false);
         setHasManageDevicePolicyManagedSubscriptionsPermission(true);
         setUpUiccSlotData();
@@ -924,7 +940,6 @@
     @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
     public void testDownloadSubscription_profileOwner_usingSwitchAfterDownload_fails()
             throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         setHasWriteEmbeddedPermission(false);
         setHasManageDevicePolicyManagedSubscriptionsPermission(true);
         setUpUiccSlotData();
@@ -953,7 +968,6 @@
     @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
     public void testDownloadSubscription_orgOwnedProfileOwner_usingSwitchAfterDownload_success()
             throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         setHasWriteEmbeddedPermission(false);
         setHasManageDevicePolicyManagedSubscriptionsPermission(true);
         setUpUiccSlotData();
@@ -979,7 +993,6 @@
     @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
     public void testDownloadSubscription_deviceOwner_usingSwitchAfterDownload_success()
             throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         setHasWriteEmbeddedPermission(false);
         setHasManageDevicePolicyManagedSubscriptionsPermission(true);
         setUpUiccSlotData();
@@ -1005,7 +1018,6 @@
     @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
     public void testDownloadSubscription_onlyAdminManagedAllowed_callerNotAdmin_error()
             throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         setHasManageDevicePolicyManagedSubscriptionsPermission(false);
         setHasWriteEmbeddedPermission(true);
         doReturn(true)
@@ -1021,27 +1033,8 @@
 
     @Test
     @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
-    public void testDownloadSubscription_onlyAdminManagedAllowed_callerNotAdmin_disabled_success()
-            throws Exception {
-        mSetFlagsRule.disableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
-        setHasManageDevicePolicyManagedSubscriptionsPermission(false);
-        setHasWriteEmbeddedPermission(true);
-        doReturn(true)
-                .when(mUserManager)
-                .hasUserRestriction(UserManager.DISALLOW_SIM_GLOBALLY);
-
-        callDownloadSubscription(SUBSCRIPTION, false /* switchAfterDownload */, true /* complete */,
-                EuiccService.RESULT_OK, 0 /* resolvableError */, "whatever" /* callingPackage */);
-
-        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
-        assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult);
-    }
-
-    @Test
-    @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
     public void testDownloadSubscription_onlyAdminManagedAllowed_callerIsAdmin_success()
             throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         setHasManageDevicePolicyManagedSubscriptionsPermission(true);
         setHasWriteEmbeddedPermission(false);
         doReturn(true)
@@ -1102,7 +1095,6 @@
 
     @Test
     public void testDeleteSubscription_adminOwned_success() throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         setHasWriteEmbeddedPermission(false);
         setHasManageDevicePolicyManagedSubscriptionsPermission(true);
         String callingPackage = "whatever";
@@ -1126,33 +1118,7 @@
     }
 
     @Test
-    public void testDeleteSubscription_adminOwned_featureDisabled_success() throws Exception {
-        mSetFlagsRule.disableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
-        setHasWriteEmbeddedPermission(true);
-        setHasManageDevicePolicyManagedSubscriptionsPermission(false);
-        String callingPackage = "whatever";
-        SubscriptionInfo subInfo1 = new SubscriptionInfo.Builder()
-                .setId(SUBSCRIPTION_ID)
-                .setEmbedded(true)
-                .setIccId(ICC_ID)
-                .setCardId(CARD_ID)
-                .setPortIndex(TelephonyManager.DEFAULT_PORT_INDEX)
-                .setGroupOwner(callingPackage)
-                .build();
-        ArrayList<SubscriptionInfo> subInfos = new ArrayList<>(Arrays.asList(subInfo1));
-        when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(subInfos);
-
-        callDeleteSubscription(
-                SUBSCRIPTION_ID, ICC_ID, true /* complete */,
-                0 /* result */, callingPackage /* callingPackage */);
-
-        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK,
-                0 /* detailedCode */);
-    }
-
-    @Test
     public void testDeleteSubscription_adminOwned_noPermissions_error() throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         setHasWriteEmbeddedPermission(false);
         setHasManageDevicePolicyManagedSubscriptionsPermission(false);
         String callingPackage = "whatever";
@@ -1940,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));
     }
@@ -1977,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);
     }
@@ -1997,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);
     }
@@ -2013,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
new file mode 100644
index 0000000..6e0d383
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccSessionTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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.euicc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.never;
+
+import android.app.PendingIntent;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.service.euicc.DownloadSubscriptionResult;
+import android.service.euicc.EuiccService;
+import android.service.euicc.GetDefaultDownloadableSubscriptionListResult;
+import android.service.euicc.GetDownloadableSubscriptionMetadataResult;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccAccessRule;
+import android.telephony.UiccCardInfo;
+import android.telephony.UiccPortInfo;
+import android.telephony.euicc.DownloadableSubscription;
+import android.telephony.euicc.EuiccInfo;
+import android.telephony.euicc.EuiccManager;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.euicc.EuiccConnector.GetOtaStatusCommandCallback;
+import com.android.internal.telephony.euicc.EuiccConnector.OtaStatusChangedCallback;
+import com.android.internal.telephony.uicc.euicc.apdu.ApduSender;
+import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.uicc.UiccSlot;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.mockito.stubbing.Stubber;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class EuiccSessionTest extends TelephonyTest {
+    @Rule
+    public final TestRule compatChangeRule = new PlatformCompatChangeRule();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    @Rule
+    public final MockitoRule rule = MockitoJUnit.rule();
+
+    private static final String SESSION_ID_1 = "SESSION_ID_1";
+    private static final String SESSION_ID_2 = "SESSION_ID_2";
+
+    private EuiccSession mEuiccSession;
+    @Mock private ApduSender mApduSender;
+    @Mock private ApduSender mApduSender2;
+
+    @Before
+    public void setUp() throws Exception {
+        mEuiccSession = new EuiccSession();
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER)
+    public void startOneSession_featureDisabled_noop() throws Exception {
+        mEuiccSession.startSession(SESSION_ID_1);
+        mEuiccSession.noteChannelOpen(mApduSender);
+        mEuiccSession.noteChannelOpen(mApduSender2);
+
+        assertThat(mEuiccSession.hasSession()).isFalse();
+
+        mEuiccSession.endSession(SESSION_ID_1);
+
+        assertThat(mEuiccSession.hasSession()).isFalse();
+        verify(mApduSender, never()).closeAnyOpenChannel();
+        verify(mApduSender2, never()).closeAnyOpenChannel();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER)
+    public void startOneSession_endSession_hasSession() throws Exception {
+        mEuiccSession.startSession(SESSION_ID_1);
+        mEuiccSession.noteChannelOpen(mApduSender);
+        mEuiccSession.noteChannelOpen(mApduSender2);
+
+        assertThat(mEuiccSession.hasSession()).isTrue();
+
+        mEuiccSession.endSession(SESSION_ID_2);
+
+        assertThat(mEuiccSession.hasSession()).isTrue();
+        verify(mApduSender, never()).closeAnyOpenChannel();
+
+        mEuiccSession.endSession(SESSION_ID_1);
+
+        assertThat(mEuiccSession.hasSession()).isFalse();
+        verify(mApduSender).closeAnyOpenChannel();
+        verify(mApduSender2).closeAnyOpenChannel();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER)
+    public void startTwoSession_endSession_hasSession() throws Exception {
+        mEuiccSession.startSession(SESSION_ID_1);
+        mEuiccSession.noteChannelOpen(mApduSender);
+        mEuiccSession.startSession(SESSION_ID_2);
+
+        assertThat(mEuiccSession.hasSession()).isTrue();
+
+        mEuiccSession.endSession(SESSION_ID_1);
+        verify(mApduSender, never()).closeAnyOpenChannel();
+
+        assertThat(mEuiccSession.hasSession()).isTrue();
+
+        mEuiccSession.endSession(SESSION_ID_2);
+
+        assertThat(mEuiccSession.hasSession()).isFalse();
+        verify(mApduSender).closeAnyOpenChannel();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_OPTIMIZATION_APDU_SENDER)
+    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);
+
+        assertThat(mEuiccSession.hasSession()).isFalse();
+
+        mEuiccSession.endSession(SESSION_ID_1);
+
+        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/GsmInboundSmsHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
index c261afe..e542a41 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java
@@ -44,6 +44,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.AsyncResult;
@@ -109,6 +110,7 @@
     private static final String RAW_TABLE_NAME = "raw";
     private static final Uri sRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI,
             RAW_TABLE_NAME);
+    private static final UserHandle MOCKED_MAIN_USER = UserHandle.of(0);
 
     private final String mMessageBody = "This is the message body of a single-part message";
     private final String mMessageBodyPart1 = "This is the first part of a multi-part message";
@@ -165,15 +167,19 @@
         mSmsFilter = Mockito.mock(InboundSmsHandler.SmsFilter.class);
         mSmsFilter2 = Mockito.mock(InboundSmsHandler.SmsFilter.class);
 
+        when(mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser()).thenReturn(true);
         doReturn(true).when(mTelephonyManager).getSmsReceiveCapableForPhone(anyInt(), anyBoolean());
         doReturn(true).when(mSmsStorageMonitor).isStorageAvailable();
 
         UserManager userManager = (UserManager)mContext.getSystemService(Context.USER_SERVICE);
         doReturn(true).when(userManager).isUserUnlocked();
+        doReturn(true).when(userManager).isUserUnlocked(any(UserHandle.class));
         doReturn(true).when(userManager).isUserRunning(any(UserHandle.class));
 
         List<UserHandle> userHandles = new ArrayList();
-        userHandles.add(UserHandle.SYSTEM);
+        userHandles.add(MOCKED_MAIN_USER);
+
+        doReturn(MOCKED_MAIN_USER).when(userManager).getMainUser();
         doReturn(userHandles).when(userManager).getUserHandles(anyBoolean());
 
         mSmsMessage.mWrappedSmsMessage = mGsmSmsMessage;
@@ -204,7 +210,7 @@
                 Telephony.Sms.CONTENT_URI.getAuthority(), mContentProvider);
 
         mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(mContext,
-                mSmsStorageMonitor, mPhone, mTestableLooper.getLooper());
+                mSmsStorageMonitor, mPhone, mTestableLooper.getLooper(), mFeatureFlags);
         mSmsFilters = new ArrayList<>();
         mSmsFilters.add(mSmsFilter);
         mSmsFilters.add(mSmsFilter2);
@@ -331,6 +337,33 @@
 
     @Test
     @MediumTest
+    public void testNewSMSProcessedThroughMainUser() {
+        transitionFromStartupToIdle();
+        UserHandle mockedMainSecUser = UserHandle.of(10);
+
+        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        List<UserHandle> userHandles = new ArrayList();
+        userHandles.add(mockedMainSecUser);
+        doReturn(mockedMainSecUser).when(userManager).getMainUser();
+        doReturn(userHandles).when(userManager).getUserHandles(anyBoolean());
+
+        // send new SMS to state machine and verify that it triggers SMS_DELIVER_ACTION
+        // and context being used is from the MAIN user
+        sendNewSms();
+
+        // Verify that the context created to send the broadcast is created from the MAIN user.
+        try {
+            verify(mContext).createPackageContextAsUser(any(String.class),
+                    any(Integer.class), eq(mockedMainSecUser));
+        } catch (PackageManager.NameNotFoundException e) {
+            fail(e.toString());
+        }
+
+        verifySmsIntentBroadcasts(0);
+    }
+
+    @Test
+    @MediumTest
     public void testNewSmsFromBlockedNumber_noBroadcastsSent() {
         String blockedNumber = "1234567890";
         mFakeBlockedNumberContentProvider.mBlockedNumbers.add(blockedNumber);
@@ -352,6 +385,7 @@
         // user locked
         UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         doReturn(false).when(userManager).isUserUnlocked();
+        doReturn(false).when(userManager).isUserUnlocked(any(UserHandle.class));
 
         transitionFromStartupToIdle();
 
@@ -366,10 +400,19 @@
         // New message notification should be shown.
         NotificationManager notificationManager =
                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-        verify(notificationManager).notify(
-                eq(InboundSmsHandler.NOTIFICATION_TAG),
-                eq(InboundSmsHandler.NOTIFICATION_ID_NEW_MESSAGE),
-                any(Notification.class));
+        if (mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser()) {
+            verify(notificationManager).notifyAsUser(
+                    eq(InboundSmsHandler.NOTIFICATION_TAG),
+                    eq(InboundSmsHandler.NOTIFICATION_ID_NEW_MESSAGE),
+                    any(Notification.class),
+                    eq(MOCKED_MAIN_USER));
+        } else {
+            verify(notificationManager).notify(
+                    eq(InboundSmsHandler.NOTIFICATION_TAG),
+                    eq(InboundSmsHandler.NOTIFICATION_ID_NEW_MESSAGE),
+                    any(Notification.class));
+
+        }
     }
 
     @Test
@@ -381,6 +424,7 @@
         // user locked
         UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         doReturn(false).when(userManager).isUserUnlocked();
+        doReturn(false).when(userManager).isUserUnlocked(any(UserHandle.class));
 
         transitionFromStartupToIdle();
 
@@ -395,10 +439,19 @@
         // No new message notification should be shown.
         NotificationManager notificationManager =
                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-        verify(notificationManager, never()).notify(
-                eq(InboundSmsHandler.NOTIFICATION_TAG),
-                eq(InboundSmsHandler.NOTIFICATION_ID_NEW_MESSAGE),
-                any(Notification.class));
+        if (mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser()) {
+            verify(notificationManager, never()).notifyAsUser(
+                    eq(InboundSmsHandler.NOTIFICATION_TAG),
+                    eq(InboundSmsHandler.NOTIFICATION_ID_NEW_MESSAGE),
+                    any(Notification.class),
+                    eq(MOCKED_MAIN_USER));
+        } else {
+            verify(notificationManager, never()).notify(
+                    eq(InboundSmsHandler.NOTIFICATION_TAG),
+                    eq(InboundSmsHandler.NOTIFICATION_ID_NEW_MESSAGE),
+                    any(Notification.class));
+
+        }
     }
 
     @Test
@@ -1042,16 +1095,23 @@
         mContentProvider.insert(sRawUri, mInboundSmsTracker.getContentValues());
 
         // user locked
-        UserManager userManager = (UserManager)mContext.getSystemService(Context.USER_SERVICE);
+        UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         doReturn(false).when(userManager).isUserUnlocked();
+        doReturn(false).when(userManager).isUserUnlocked(any(UserHandle.class));
 
-        SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
+        SmsBroadcastUndelivered.initialize(
+                mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler, mFeatureFlags);
 
-        // verify that a broadcast receiver is registered for current user (user == null) based on
-        // implementation in ContextFixture. registerReceiver may be called more than once (for
-        // example by GsmInboundSmsHandler if TEST_MODE is true)
-        verify(mContext, atLeastOnce()).registerReceiver(any(BroadcastReceiver.class),
-                any(IntentFilter.class));
+        if (mFeatureFlags.smsMmsDeliverBroadcastsRedirectToMainUser()) {
+            verify(mContext).registerReceiverAsUser(any(BroadcastReceiver.class),
+                    any(UserHandle.class), any(IntentFilter.class), any(), any());
+        } else {
+            // verify that a broadcast receiver is registered for current user (user == null) based
+            // on implementation in ContextFixture. registerReceiver may be called more than once
+            // (for example by GsmInboundSmsHandler if TEST_MODE is true)
+            verify(mContext, atLeastOnce()).registerReceiver(any(BroadcastReceiver.class),
+                    any(IntentFilter.class));
+        }
 
         // wait for ScanRawTableThread
         waitForMs(100);
@@ -1062,7 +1122,10 @@
 
         // when user unlocks the device, the message in db should be broadcast
         doReturn(true).when(userManager).isUserUnlocked();
-        mContext.sendBroadcast(new Intent(Intent.ACTION_USER_UNLOCKED));
+        doReturn(true).when(userManager).isUserUnlocked(any(UserHandle.class));
+        mContext.sendBroadcast(
+                new Intent(Intent.ACTION_USER_UNLOCKED)
+                        .putExtra(Intent.EXTRA_USER_HANDLE, MOCKED_MAIN_USER.getIdentifier()));
         // wait for ScanRawTableThread
         waitForMs(100);
         processAllMessages();
@@ -1097,7 +1160,8 @@
         // add a fake entry to db
         mContentProvider.insert(sRawUri, mInboundSmsTracker.getContentValues());
 
-        SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
+        SmsBroadcastUndelivered.initialize(
+                mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler, mFeatureFlags);
 
         // wait for ScanRawTableThread
         waitForMs(100);
@@ -1112,7 +1176,8 @@
     @MediumTest
     public void testBroadcastUndeliveredDeleted() throws Exception {
         replaceInstance(SmsBroadcastUndelivered.class, "instance", null, null);
-        SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
+        SmsBroadcastUndelivered.initialize(
+                mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler, mFeatureFlags);
         mInboundSmsTracker = new InboundSmsTracker(
                 mContext,
                 mSmsPdu, /* pdu */
@@ -1138,7 +1203,9 @@
         mContentProvider.insert(sRawUri, rawSms);
 
         //when user unlocks the device, broadcast should not be sent for new message
-        mContext.sendBroadcast(new Intent(Intent.ACTION_USER_UNLOCKED));
+        mContext.sendBroadcast(
+                new Intent(Intent.ACTION_USER_UNLOCKED)
+                        .putExtra(Intent.EXTRA_USER_HANDLE, MOCKED_MAIN_USER.getIdentifier()));
         // wait for ScanRawTableThread
         waitForMs(100);
         processAllMessages();
@@ -1165,7 +1232,8 @@
                 when(mTelephonyComponentFactory).makeInboundSmsTracker(any(Context.class),
                         any(Cursor.class), anyBoolean());
 
-        SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
+        SmsBroadcastUndelivered.initialize(
+                mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler, mFeatureFlags);
         // wait for ScanRawTableThread
         waitForMs(100);
         processAllMessages();
@@ -1183,7 +1251,8 @@
         mContentProvider.insert(sRawUri, mInboundSmsTracker.getContentValues());
         mContentProvider.insert(sRawUri, mInboundSmsTrackerSub1.getContentValues());
 
-        SmsBroadcastUndelivered.initialize(mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
+        SmsBroadcastUndelivered.initialize(
+                mContext, mGsmInboundSmsHandler, mCdmaInboundSmsHandler, mFeatureFlags);
         // wait for ScanRawTableThread
         waitForMs(100);
         processAllMessages();
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 8374daa..639a2a3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsDispatcherTest.java
@@ -45,6 +45,7 @@
 import android.content.pm.ServiceInfo;
 import android.location.Country;
 import android.location.CountryDetector;
+import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.Message;
 import android.os.RemoteException;
@@ -114,6 +115,7 @@
 
     private GsmSMSDispatcher mGsmSmsDispatcher;
     private GsmSmsDispatcherTestHandler mGsmSmsDispatcherTestHandler;
+    private int mCallingUserId;
 
     private class GsmSmsDispatcherTestHandler extends HandlerThread {
 
@@ -149,6 +151,7 @@
         waitUntilReady();
         mGsmSmsDispatcher = new GsmSMSDispatcher(mPhone, mSmsDispatchersController,
                 mGsmInboundSmsHandler);
+        mCallingUserId = Binder.getCallingUserHandle().getIdentifier();
         processAllMessages();
     }
 
@@ -177,7 +180,7 @@
                 .thenReturn(new Country("US", Country.COUNTRY_SOURCE_SIM));
 
         mGsmSmsDispatcher.sendText("6501002000", "121" /*scAddr*/, "test sms",
-                null, null, null, null, false, -1, false, -1, false, 0L);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
 
         verify(mSimulatedCommandsVerifier).sendSMS(anyString(), anyString(), any(Message.class));
         // Blocked number provider is notified about the emergency contact asynchronously.
@@ -197,7 +200,7 @@
 
         mGsmSmsDispatcher.sendText(
                 getEmergencyNumberFromSystemPropertiesOrDefault(), "121" /*scAddr*/, "test sms",
-                null, null, null, null, false, -1, false, -1, false, 0L);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
 
         verify(mSimulatedCommandsVerifier).sendSMS(anyString(), anyString(), any(Message.class));
         // Blocked number provider is notified about the emergency contact asynchronously.
@@ -245,7 +248,7 @@
         // send invalid dest address: +
         mReceivedTestIntent = false;
         mGsmSmsDispatcher.sendText("+", "222" /*scAddr*/, TAG,
-                pendingIntent, null, null, null, false, -1, false, -1, false, 0L);
+                pendingIntent, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
         waitForMs(500);
         verify(mSimulatedCommandsVerifier, times(0)).sendSMS(anyString(), anyString(),
                 any(Message.class));
@@ -310,7 +313,7 @@
         // send SMS and check sentIntent
         mReceivedTestIntent = false;
         mGsmSmsDispatcher.sendMultipartText("+123" /*destAddr*/, "222" /*scAddr*/, parts,
-                sentIntents, null, null, null, false, -1, false, -1, 0L);
+                sentIntents, null, null, null, mCallingUserId, false, -1, false, -1, 0L, 0L);
 
         waitForMs(500);
         synchronized (mLock) {
@@ -390,7 +393,7 @@
         mReceivedTestIntent = false;
 
         mGsmSmsDispatcher.sendText("6501002000", "121" /*scAddr*/, "test sms",
-                pendingIntent, null, null, null, false, -1, false, -1, false, 0L);
+                pendingIntent, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
         processAllMessages();
         synchronized (mLock) {
             if (!mReceivedTestIntent) {
@@ -414,7 +417,7 @@
         mGsmSmsDispatcher.mCarrierMessagingTimeout = 100;
 
         mGsmSmsDispatcher.sendText("6501002000", "121" /*scAddr*/, "test sms",
-                null, null, null, null, false, -1, false, -1, false, 0L);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
         // wait for timeout
         waitForMs(150);
         verify(mSimulatedCommandsVerifier).sendSMS(anyString(), anyString(), any(Message.class));
@@ -428,7 +431,7 @@
         mockUiccWithCarrierApp();
 
         mGsmSmsDispatcher.sendText("6501002000", "121" /*scAddr*/, "test sms",
-                null, null, null, null, false, -1, false, -1, false, 0L);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
         processAllMessages();
         verify(mSimulatedCommandsVerifier).sendSMS(anyString(), anyString(), any(Message.class));
     }
@@ -452,7 +455,8 @@
         sentIntents.add(sentIntent2);
 
         mGsmSmsDispatcher.sendMultipartText("6501002000" /*destAddr*/, "222" /*scAddr*/, parts,
-                withSentIntents ? sentIntents : null, null, null, null, false, -1, false, -1, 0L);
+                withSentIntents ? sentIntents : null, null, null, null, mCallingUserId,
+                false, -1, false, -1, 0L, 0L);
     }
 
     @Test
@@ -524,7 +528,7 @@
         }
 
         mGsmSmsDispatcher.sendText("111", "222" /*scAddr*/, TAG,
-                null, null, null, null, false, -1, false, -1, false, 0L);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
 
         ArgumentCaptor<String> pduCaptor = ArgumentCaptor.forClass(String.class);
         verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor.capture(),
@@ -545,7 +549,7 @@
             messageRef += parts.size();
         }
         mGsmSmsDispatcher.sendMultipartText("6501002000" /*destAddr*/, "222" /*scAddr*/, parts,
-                null, null, null, null, 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);
 
@@ -561,14 +565,14 @@
     @Test
     public void testSendTextWithMessageRefNegativeBoundaryCondition() throws Exception {
         mIsimUiccRecords = new IsimUiccRecords(mUiccCardApplication3gpp, mContext,
-                mSimulatedCommands);
+                mSimulatedCommands, mFeatureFlags);
         doReturn(mIsimUiccRecords).when(mPhone).getIccRecords();
         Message msg = mGsmSmsDispatcher.obtainMessage(17);
         mPhone.getIccRecords().setSmssTpmrValue(-1, msg);
         mSubscriptionManagerService.setLastUsedTPMessageReference(mPhone.getSubId(), -1);
 
         mGsmSmsDispatcher.sendText("111", "222" /*scAddr*/, TAG,
-                null, null, null, null, false, -1, false, -1, false, 0L);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
 
         ArgumentCaptor<String> pduCaptor = ArgumentCaptor.forClass(String.class);
         verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor.capture(),
@@ -580,12 +584,12 @@
     @Test
     public void testSendTextWithMessageRefMaxBoundaryCondition() throws Exception {
         mIsimUiccRecords = new IsimUiccRecords(mUiccCardApplication3gpp, mContext,
-                mSimulatedCommands);
+                mSimulatedCommands, mFeatureFlags);
         doReturn(mIsimUiccRecords).when(mPhone).getIccRecords();
         Message msg = mGsmSmsDispatcher.obtainMessage(17);
         mPhone.getIccRecords().setSmssTpmrValue(255, msg);
         mGsmSmsDispatcher.sendText("111", "222" /*scAddr*/, TAG,
-                null, null, null, null, false, -1, false, -1, false, 0L);
+                null, null, null, null, mCallingUserId, false, -1, false, -1, false, 0L);
 
         ArgumentCaptor<String> pduCaptor = ArgumentCaptor.forClass(String.class);
         verify(mSimulatedCommandsVerifier).sendSMS(anyString(), pduCaptor.capture(),
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsNrSaModeHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsNrSaModeHandlerTest.java
index 3b362b1..8cf422e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsNrSaModeHandlerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsNrSaModeHandlerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony.imsphone;
 
-import static android.telephony.CarrierConfigManager.CARRIER_NR_AVAILABILITY_NSA;
 import static android.telephony.CarrierConfigManager.CARRIER_NR_AVAILABILITY_SA;
 import static android.telephony.CarrierConfigManager.Ims.KEY_NR_SA_DISABLE_POLICY_INT;
 import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_NONE;
@@ -24,6 +23,7 @@
 import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_WFC_ESTABLISHED;
 import static android.telephony.CarrierConfigManager.Ims.NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED;
 import static android.telephony.CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY;
+import static android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
 
@@ -32,6 +32,7 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.os.Handler;
@@ -55,7 +56,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Arrays;
 import java.util.Set;
 
 @RunWith(AndroidTestingRunner.class)
@@ -69,7 +69,7 @@
     private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener;
     private Handler mPreciseCallStateHandler;
 
-    private Phone mPassthroughPhone;
+    private Phone mDefaultPhone;
 
     @Mock private ImsPhoneCall mForegroundCall;
     @Mock private ImsPhoneCall mBackgroundCall;
@@ -77,8 +77,7 @@
     private Call.State mIdleState = ImsPhoneCall.State.IDLE;
 
     private int mAnyInt = 0;
-    private final Set<String> mFeatureTags =
-            new ArraySet<String>(Arrays.asList(ImsNrSaModeHandler.MMTEL_FEATURE_TAG));
+    private final Set<String> mFeatureTags = new ArraySet<String>();
 
     @Before
     public void setUp() throws Exception {
@@ -96,12 +95,12 @@
         doReturn(mContextFixture.getCarrierConfigBundle()).when(mCarrierConfigManager)
                 .getConfigForSubId(anyInt(), any());
 
-        mPassthroughPhone = new GsmCdmaPhone(
+        mDefaultPhone = new GsmCdmaPhone(
                 mContext, mSimulatedCommands, mNotifier, true, 0,
                 PhoneConstants.PHONE_TYPE_GSM, mTelephonyComponentFactory,
                 (c, p) -> mImsManager, mFeatureFlags);
 
-        doReturn(mPassthroughPhone).when(mImsPhone).getDefaultPhone();
+        doReturn(mDefaultPhone).when(mImsPhone).getDefaultPhone();
 
         doReturn(mForegroundCall).when(mImsPhone).getForegroundCall();
         doReturn(mBackgroundCall).when(mImsPhone).getBackgroundCall();
@@ -140,19 +139,19 @@
     }
 
     @Test
-    public void testOnImsRegisteredWithSaDisablePolicyNone() {
+    public void testOnImsRegisteredWithNrSaCapabilityAndVoiceCapabilityAndSaDisablePolicyNone() {
         mContextFixture.getCarrierConfigBundle().putInt(
                 KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_NONE);
         mContextFixture.getCarrierConfigBundle().putIntArray(
                 KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
 
         mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
-
-        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
+        mTestImsNrSaModeHandler.updateImsCapability(CAPABILITY_TYPE_VOICE);
+        mTestImsNrSaModeHandler.setWifiRegStatus(false);
 
         mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
 
-        assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered());
+        assertFalse(mTestImsNrSaModeHandler.isWifiRegistered());
     }
 
     @Test
@@ -163,17 +162,24 @@
                 KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
 
         mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+        mTestImsNrSaModeHandler.updateImsCapability(CAPABILITY_TYPE_VOICE);
 
         verify(mImsPhone).registerForPreciseCallStateChanged(any(), anyInt(), any());
 
         mSimulatedCommands.setN1ModeEnabled(false, null);
-        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+        mTestImsNrSaModeHandler.setNrSaDisabledForWfc(true);
+        mTestImsNrSaModeHandler.setWifiRegStatus(true);
         mTestImsNrSaModeHandler.setImsCallStatus(true);
 
         mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_NONE, mFeatureTags);
 
-        assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered());
         assertTrue(mSimulatedCommands.isN1ModeEnabled());
+        assertFalse(mTestImsNrSaModeHandler.isWifiRegistered());
+
+        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
+
+        assertFalse(mSimulatedCommands.isN1ModeEnabled());
+        assertTrue(mTestImsNrSaModeHandler.isWifiRegistered());
     }
 
     @Test
@@ -185,54 +191,42 @@
                 KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
 
         mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+        mTestImsNrSaModeHandler.updateImsCapability(CAPABILITY_TYPE_VOICE);
 
         verify(mImsPhone).registerForPreciseCallStateChanged(any(), anyInt(), any());
 
         mSimulatedCommands.setN1ModeEnabled(true, null);
-        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
+        mTestImsNrSaModeHandler.setWifiRegStatus(false);
         mTestImsNrSaModeHandler.setImsCallStatus(true);
         mSimulatedCommands.setVonrEnabled(true);
 
         mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
         processAllMessages();
 
-        assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered());
         assertTrue(mSimulatedCommands.isN1ModeEnabled());
+        assertTrue(mTestImsNrSaModeHandler.isWifiRegistered());
 
         mSimulatedCommands.setN1ModeEnabled(true, null);
-        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
+        mTestImsNrSaModeHandler.setWifiRegStatus(false);
         mTestImsNrSaModeHandler.setImsCallStatus(true);
         mSimulatedCommands.setVonrEnabled(false);
 
         mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
         processAllMessages();
 
-        assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered());
         assertFalse(mSimulatedCommands.isN1ModeEnabled());
+        assertTrue(mTestImsNrSaModeHandler.isWifiRegistered());
 
         mSimulatedCommands.setN1ModeEnabled(true, null);
-        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
+        mTestImsNrSaModeHandler.setWifiRegStatus(false);
         mTestImsNrSaModeHandler.setImsCallStatus(true);
-        mSimulatedCommands.setVonrEnabled(false);
+        mSimulatedCommands.setVonrEnabled(true);
 
-        mFeatureTags.remove(ImsNrSaModeHandler.MMTEL_FEATURE_TAG);
-        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
+        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_NONE, mFeatureTags);
         processAllMessages();
 
-        assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered());
         assertTrue(mSimulatedCommands.isN1ModeEnabled());
-
-        mSimulatedCommands.setN1ModeEnabled(true, null);
-        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
-        mTestImsNrSaModeHandler.setImsCallStatus(true);
-        mSimulatedCommands.setVonrEnabled(false);
-
-        mFeatureTags.add(ImsNrSaModeHandler.MMTEL_FEATURE_TAG);
-        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
-        processAllMessages();
-
-        assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered());
-        assertFalse(mSimulatedCommands.isN1ModeEnabled());
+        assertFalse(mTestImsNrSaModeHandler.isWifiRegistered());
     }
 
     @Test
@@ -243,26 +237,50 @@
                 KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
 
         mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+        mTestImsNrSaModeHandler.updateImsCapability(CAPABILITY_TYPE_VOICE);
+
+        verify(mImsPhone).unregisterForPreciseCallStateChanged(mTestImsNrSaModeHandler);
 
         mSimulatedCommands.setN1ModeEnabled(true, null);
-        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
+        mTestImsNrSaModeHandler.setWifiRegStatus(false);
 
         mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
 
-        assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered());
         assertFalse(mSimulatedCommands.isN1ModeEnabled());
+        assertTrue(mTestImsNrSaModeHandler.isWifiRegistered());
 
         mSimulatedCommands.setN1ModeEnabled(false, null);
-        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+        mTestImsNrSaModeHandler.setWifiRegStatus(true);
 
         mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_NONE, mFeatureTags);
 
-        assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered());
         assertTrue(mSimulatedCommands.isN1ModeEnabled());
+        assertFalse(mTestImsNrSaModeHandler.isWifiRegistered());
     }
 
     @Test
-    public void testOnImsUnregisteredDoNothingIfNotVowifiRegNoti() {
+    public void testOnImsRegisteredWithoutNrSaCapabilityWithSaDisablePolicyVowifiRegistered() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+        mTestImsNrSaModeHandler.updateImsCapability(CAPABILITY_TYPE_VOICE);
+
+        verify(mImsPhone, times(0)).unregisterForPreciseCallStateChanged(mTestImsNrSaModeHandler);
+
+        mSimulatedCommands.setN1ModeEnabled(true, null);
+        mTestImsNrSaModeHandler.setWifiRegStatus(false);
+
+        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
+
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+        assertFalse(mTestImsNrSaModeHandler.isWifiRegistered());
+    }
+
+    @Test
+    public void testOnImsRegisteredWithoutVoiceCapabilityWithSaDisablePolicyVowifiRegistered() {
         mContextFixture.getCarrierConfigBundle().putInt(
                 KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED);
         mContextFixture.getCarrierConfigBundle().putIntArray(
@@ -270,37 +288,100 @@
 
         mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
 
-        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+        verify(mImsPhone).unregisterForPreciseCallStateChanged(mTestImsNrSaModeHandler);
 
-        mTestImsNrSaModeHandler.onImsUnregistered(REGISTRATION_TECH_NONE);
+        mSimulatedCommands.setN1ModeEnabled(true, null);
+        mTestImsNrSaModeHandler.setWifiRegStatus(false);
 
-        assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered());
+        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_IWLAN, mFeatureTags);
+
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+        assertTrue(mTestImsNrSaModeHandler.isWifiRegistered());
     }
 
     @Test
-    public void testOnImsUnregisteredWithSaDisablePolicyVowifiRegistered() {
+    public void testOnImsUnregisteredWithoutNrSaCapability() {
         mContextFixture.getCarrierConfigBundle().putInt(
                 KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED);
         mContextFixture.getCarrierConfigBundle().putIntArray(
-                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{});
 
         mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
 
-        mSimulatedCommands.setN1ModeEnabled(false, null);
-        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+        mTestImsNrSaModeHandler.setWifiRegStatus(true);
 
         mTestImsNrSaModeHandler.onImsUnregistered(REGISTRATION_TECH_IWLAN);
 
-        assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered());
-        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+        assertTrue(mTestImsNrSaModeHandler.isWifiRegistered());
+    }
+
+
+    @Test
+    public void testOnImsUnregisteredWithSaDisablePolicyNone() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_NONE);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+
+        mTestImsNrSaModeHandler.setWifiRegStatus(true);
+
+        mTestImsNrSaModeHandler.onImsUnregistered(REGISTRATION_TECH_IWLAN);
+
+        assertTrue(mTestImsNrSaModeHandler.isWifiRegistered());
+    }
+
+    @Test
+    public void testOnImsUnregisteredWithoutRadioTechIwlan() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+
+        mTestImsNrSaModeHandler.setWifiRegStatus(true);
+
+        mTestImsNrSaModeHandler.onImsUnregistered(NR_SA_DISABLE_POLICY_NONE);
+
+        assertTrue(mTestImsNrSaModeHandler.isWifiRegistered());
+    }
+
+    @Test
+    public void testOnImsUnregisteredWithoutWifiResistring() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+
+        mTestImsNrSaModeHandler.setWifiRegStatus(false);
+
+        mTestImsNrSaModeHandler.onImsUnregistered(REGISTRATION_TECH_IWLAN);
+
+        assertFalse(mTestImsNrSaModeHandler.isWifiRegistered());
+    }
+
+    @Test
+    public void testOnImsUnregisteredWithNrSaCapabilityAndVoiceCapabilityAndRadioTechIwlan() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_VOWIFI_REGISTERED);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+        mTestImsNrSaModeHandler.updateImsCapability(CAPABILITY_TYPE_VOICE);
 
         mSimulatedCommands.setN1ModeEnabled(false, null);
-        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+        mTestImsNrSaModeHandler.setNrSaDisabledForWfc(true);
+        mTestImsNrSaModeHandler.setWifiRegStatus(true);
 
-        mTestImsNrSaModeHandler.onImsUnregistered(REGISTRATION_TECH_NONE);
+        mTestImsNrSaModeHandler.onImsUnregistered(REGISTRATION_TECH_IWLAN);
 
-        assertTrue(mTestImsNrSaModeHandler.isVowifiRegistered());
-        assertFalse(mSimulatedCommands.isN1ModeEnabled());
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+        assertFalse(mTestImsNrSaModeHandler.isWifiRegistered());
     }
 
     @Test
@@ -311,12 +392,13 @@
                 KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
 
         mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+        mTestImsNrSaModeHandler.updateImsCapability(CAPABILITY_TYPE_VOICE);
 
         verify(mImsPhone).registerForPreciseCallStateChanged(
                 mPreciseCallStateHandlerCaptor.capture(), anyInt(), any());
         mPreciseCallStateHandler = mPreciseCallStateHandlerCaptor.getValue();
 
-        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+        mTestImsNrSaModeHandler.setWifiRegStatus(true);
         mSimulatedCommands.setN1ModeEnabled(true, null);
 
         mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101));
@@ -324,38 +406,55 @@
         assertTrue(mTestImsNrSaModeHandler.isImsCallOngoing());
         assertFalse(mSimulatedCommands.isN1ModeEnabled());
 
-        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
-        mSimulatedCommands.setN1ModeEnabled(true, null);
-
-        doReturn(mActiveState).when(mForegroundCall).getState();
-        doReturn(mActiveState).when(mBackgroundCall).getState();
-        mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101));
-
-        assertTrue(mTestImsNrSaModeHandler.isImsCallOngoing());
-        assertTrue(mSimulatedCommands.isN1ModeEnabled());
-
-        mTestImsNrSaModeHandler.setVowifiRegStatus(false);
-        mTestImsNrSaModeHandler.setImsCallStatus(false);
-        mSimulatedCommands.setN1ModeEnabled(true, null);
-
         doReturn(mIdleState).when(mForegroundCall).getState();
         doReturn(mIdleState).when(mBackgroundCall).getState();
         mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101));
 
         assertFalse(mTestImsNrSaModeHandler.isImsCallOngoing());
         assertTrue(mSimulatedCommands.isN1ModeEnabled());
-
-        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
-        mTestImsNrSaModeHandler.setImsCallStatus(true);
-        mSimulatedCommands.setN1ModeEnabled(false, null);
-        mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101));
-
-        assertFalse(mTestImsNrSaModeHandler.isImsCallOngoing());
-        assertTrue(mSimulatedCommands.isN1ModeEnabled());
     }
 
     @Test
-    public void testUnregisterForPreciseCallStateChangeIfNeeded() {
+    public void testOnPreciseCallStateChangedWithSaDisablePolicyWfcEstablishedWithVonrDisabled() {
+        mContextFixture.getCarrierConfigBundle().putInt(
+                KEY_NR_SA_DISABLE_POLICY_INT,
+                NR_SA_DISABLE_POLICY_WFC_ESTABLISHED_WHEN_VONR_DISABLED);
+        mContextFixture.getCarrierConfigBundle().putIntArray(
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
+
+        mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
+        mTestImsNrSaModeHandler.updateImsCapability(CAPABILITY_TYPE_VOICE);
+
+        verify(mImsPhone).registerForPreciseCallStateChanged(
+                mPreciseCallStateHandlerCaptor.capture(), anyInt(), any());
+        mPreciseCallStateHandler = mPreciseCallStateHandlerCaptor.getValue();
+
+        mTestImsNrSaModeHandler.setWifiRegStatus(true);
+        mSimulatedCommands.setN1ModeEnabled(true, null);
+        mSimulatedCommands.setVonrEnabled(false);
+
+        mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101));
+        processAllMessages();
+
+        assertFalse(mSimulatedCommands.isN1ModeEnabled());
+
+        doReturn(mIdleState).when(mForegroundCall).getState();
+        doReturn(mIdleState).when(mBackgroundCall).getState();
+        mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101));
+
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+
+        doReturn(mActiveState).when(mForegroundCall).getState();
+        doReturn(mActiveState).when(mBackgroundCall).getState();
+        mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101));
+        mTestImsNrSaModeHandler.setImsCallStatus(false);
+        processAllMessages();
+
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+    }
+
+    @Test
+    public void testOnCarrierConfigChangedRegisterOrUnregisterListenerForPreciseCallStateChange() {
         mContextFixture.getCarrierConfigBundle().putInt(
                 KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_WFC_ESTABLISHED);
         mContextFixture.getCarrierConfigBundle().putIntArray(
@@ -376,20 +475,29 @@
     }
 
     @Test
-    public void testNrSaModeIsNotHandledWhenNotSupported() {
+    public void testUpdateImsCapabilityWithSaDisablePolicyWfcEstablished() {
         mContextFixture.getCarrierConfigBundle().putInt(
                 KEY_NR_SA_DISABLE_POLICY_INT, NR_SA_DISABLE_POLICY_WFC_ESTABLISHED);
         mContextFixture.getCarrierConfigBundle().putIntArray(
-                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_NSA});
+                KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY, new int[]{CARRIER_NR_AVAILABILITY_SA});
 
         mCarrierConfigChangeListener.onCarrierConfigChanged(mAnyInt, mAnyInt, mAnyInt, mAnyInt);
 
-        mSimulatedCommands.setN1ModeEnabled(false, null);
-        mTestImsNrSaModeHandler.setVowifiRegStatus(true);
+        verify(mImsPhone).registerForPreciseCallStateChanged(
+                mPreciseCallStateHandlerCaptor.capture(), anyInt(), any());
+        mPreciseCallStateHandler = mPreciseCallStateHandlerCaptor.getValue();
 
-        mTestImsNrSaModeHandler.onImsRegistered(REGISTRATION_TECH_NONE, mFeatureTags);
+        mTestImsNrSaModeHandler.setWifiRegStatus(true);
+        mSimulatedCommands.setN1ModeEnabled(true, null);
+        mPreciseCallStateHandler.handleMessage(mPreciseCallStateHandler.obtainMessage(101));
 
-        assertFalse(mTestImsNrSaModeHandler.isVowifiRegistered());
+        assertTrue(mTestImsNrSaModeHandler.isImsCallOngoing());
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
+
+        mTestImsNrSaModeHandler.updateImsCapability(CAPABILITY_TYPE_VOICE);
         assertFalse(mSimulatedCommands.isN1ModeEnabled());
+
+        mTestImsNrSaModeHandler.updateImsCapability(0);
+        assertTrue(mSimulatedCommands.isN1ModeEnabled());
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
index 6056112..a74a408 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneCallTrackerTest.java
@@ -1477,6 +1477,17 @@
 
     @Test
     @SmallTest
+    public void testSipRequestCancelled() {
+        doReturn(true).when(mFeatureFlags).remapDisconnectCauseSipRequestCancelled();
+
+        assertEquals(DisconnectCause.NORMAL,
+                mCTUT.getDisconnectCauseFromReasonInfo(
+                        new ImsReasonInfo(ImsReasonInfo.CODE_SIP_REQUEST_CANCELLED, 0),
+                        Call.State.DIALING));
+    }
+
+    @Test
+    @SmallTest
     public void testLowBatteryDisconnectDialing() {
         assertEquals(DisconnectCause.DIAL_LOW_BATTERY, mCTUT.getDisconnectCauseFromReasonInfo(
                 new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_LOW_BATTERY, 0), Call.State.DIALING));
@@ -2774,6 +2785,32 @@
         assertNetworkStatsEquals(expectedStats, uidStatsCaptor.getValue());
     }
 
+    @Test
+    public void testPreventHangupDuringCallMerge() {
+        // Enable feature flag
+        doReturn(true).when(mFeatureFlags).preventHangupDuringCallMerge();
+
+        // Change carrier config to allow call hold for 2nd call setup
+        PersistableBundle bundle = mContextFixture.getCarrierConfigBundle();
+        bundle.putBoolean(CarrierConfigManager.KEY_ALLOW_HOLD_VIDEO_CALL_BOOL, true);
+        mCTUT.updateCarrierConfigCache(bundle);
+
+        // Place a call.
+        placeCallAndMakeActive();
+        // Place a 2nd call
+        ImsPhoneConnection imsPhoneConnection = placeCallAndMakeActive();
+
+        // Try call merge
+        mCTUT.conference();
+
+        try {
+            mCTUT.hangup(imsPhoneConnection.getCall());
+            fail("Expect CallStateException but not");
+        } catch  (CallStateException e) {
+            // Expected exception
+        }
+    }
+
     private ImsPhoneConnection placeCallAndMakeActive() {
         ImsPhoneConnection connection = placeCall();
         ImsCall imsCall = connection.getImsCall();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java
index 3c1b045..6f42350 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneMmiCodeTest.java
@@ -19,10 +19,13 @@
 import static junit.framework.Assert.fail;
 
 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.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.os.AsyncResult;
@@ -35,6 +38,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.flags.FeatureFlags;
 
@@ -54,6 +58,9 @@
     private static final String TEST_DIAL_STRING_SERVICE_CODE = "*67911";
     private static final String TEST_DIAL_STRING_NO_SERVICE_CODE = "*767911";
     private static final String TEST_DIAL_STRING_NON_EMERGENCY_NUMBER = "11976";
+    private static final String TEST_DIAL_NUMBER = "123456789";
+    private static final String TEST_DIAL_STRING_CFNRY_ACTIVE_CODE = "**61*" + TEST_DIAL_NUMBER;
+    private static final String TEST_DIAL_STRING_CFNRY_DEACTIVE_CODE = "##61#";
     private ImsPhoneMmiCode mImsPhoneMmiCode;
     private ImsPhone mImsPhoneUT;
 
@@ -90,7 +97,7 @@
         // Test *67911 is treated as temporary mode CLIR
         doReturn(true).when(mTelephonyManager).isEmergencyNumber(TEST_DIAL_STRING_SERVICE_CODE);
         mImsPhoneMmiCode = ImsPhoneMmiCode.newFromDialString(TEST_DIAL_STRING_SERVICE_CODE,
-                mImsPhoneUT);
+                mImsPhoneUT, mFeatureFlags);
         assertTrue(mImsPhoneMmiCode.isTemporaryModeCLIR());
     }
 
@@ -99,7 +106,7 @@
         // Test if prefix isn't *67 or *82
         doReturn(true).when(mTelephonyManager).isEmergencyNumber(TEST_DIAL_STRING_NO_SERVICE_CODE);
         mImsPhoneMmiCode = ImsPhoneMmiCode.newFromDialString(TEST_DIAL_STRING_NO_SERVICE_CODE,
-                mImsPhoneUT);
+                mImsPhoneUT, mFeatureFlags);
         assertTrue(mImsPhoneMmiCode == null);
     }
 
@@ -107,13 +114,14 @@
     public void testIsTemporaryModeCLIRForNonEmergencyNumber() {
         // Test if dialing string isn't an emergency number
         mImsPhoneMmiCode = ImsPhoneMmiCode.newFromDialString(TEST_DIAL_STRING_NON_EMERGENCY_NUMBER,
-                mImsPhoneUT);
+                mImsPhoneUT, mFeatureFlags);
         assertTrue(mImsPhoneMmiCode == null);
     }
 
     @Test
     public void testNoCrashOnEmptyMessage() {
-        ImsPhoneMmiCode mmi = ImsPhoneMmiCode.newNetworkInitiatedUssd(null, true, mImsPhoneUT);
+        ImsPhoneMmiCode mmi = ImsPhoneMmiCode.newNetworkInitiatedUssd(null, true, mImsPhoneUT,
+                mFeatureFlags);
         try {
             mmi.onUssdFinishedError();
         } catch (Exception e) {
@@ -134,7 +142,8 @@
     @Test
     @SmallTest
     public void testOperationNotSupported() {
-        mImsPhoneMmiCode = ImsPhoneMmiCode.newNetworkInitiatedUssd(null, true, mImsPhoneUT);
+        mImsPhoneMmiCode = ImsPhoneMmiCode.newNetworkInitiatedUssd(null, true, mImsPhoneUT,
+                mFeatureFlags);
 
         // Emulate request not supported from the network.
         AsyncResult ar = new AsyncResult(null, null,
@@ -143,4 +152,80 @@
         verify(mContext.getResources()).getText(
                 eq(com.android.internal.R.string.mmiErrorNotSupported));
     }
+
+    @Test
+    @SmallTest
+    public void testActivationCfnrWithCfnry() throws Exception {
+        doNothing().when(mImsPhone).setCallForwardingOption(
+                anyInt(), anyInt(), any(), anyInt(), anyInt(), any());
+
+        doReturn(true).when(mFeatureFlags).useCarrierConfigForCfnryTimeViaMmi();
+
+        int carrierConfigTime = 40;
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt(CarrierConfigManager
+                .KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT, carrierConfigTime);
+        doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+
+        int mmiTime = 30;
+        String withoutTimeCode = TEST_DIAL_STRING_CFNRY_ACTIVE_CODE + "#";
+        String withTimeCode = TEST_DIAL_STRING_CFNRY_ACTIVE_CODE + "**" + mmiTime + "#";
+
+        mImsPhoneMmiCode = ImsPhoneMmiCode.newFromDialString(withoutTimeCode, mImsPhoneUT,
+                mFeatureFlags);
+        // For verification, replace the internal object of target with mock
+        replaceInstance(ImsPhoneMmiCode.class, "mPhone", mImsPhoneMmiCode, mImsPhone);
+
+        mImsPhoneMmiCode.processCode();
+        verify(mImsPhone, times(1)).setCallForwardingOption(
+                eq(CommandsInterface.CF_ACTION_REGISTRATION),
+                anyInt(),
+                eq(TEST_DIAL_NUMBER),
+                anyInt(),
+                eq(carrierConfigTime),
+                any());
+
+        mImsPhoneMmiCode = ImsPhoneMmiCode.newFromDialString(withTimeCode, mImsPhoneUT,
+                mFeatureFlags);
+        // For verification, replace the internal object of target with mock
+        replaceInstance(ImsPhoneMmiCode.class, "mPhone", mImsPhoneMmiCode, mImsPhone);
+
+        mImsPhoneMmiCode.processCode();
+        verify(mImsPhone, times(1)).setCallForwardingOption(
+                eq(CommandsInterface.CF_ACTION_REGISTRATION),
+                anyInt(),
+                eq(TEST_DIAL_NUMBER),
+                anyInt(),
+                eq(mmiTime),
+                any());
+    }
+
+    @Test
+    @SmallTest
+    public void testDeactivationCfnrWithCfnry() throws Exception {
+        doNothing().when(mImsPhone).setCallForwardingOption(
+                anyInt(), anyInt(), any(), anyInt(), anyInt(), any());
+
+        doReturn(true).when(mFeatureFlags).useCarrierConfigForCfnryTimeViaMmi();
+
+        int carrierConfigTime = 40;
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt(CarrierConfigManager
+                .KEY_NO_REPLY_TIMER_FOR_CFNRY_SEC_INT, carrierConfigTime);
+        doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
+
+        mImsPhoneMmiCode = ImsPhoneMmiCode.newFromDialString(TEST_DIAL_STRING_CFNRY_DEACTIVE_CODE,
+                mImsPhoneUT, mFeatureFlags);
+        // For verification, replace the internal object of target with mock
+        replaceInstance(ImsPhoneMmiCode.class, "mPhone", mImsPhoneMmiCode, mImsPhone);
+
+        mImsPhoneMmiCode.processCode();
+        verify(mImsPhone, times(1)).setCallForwardingOption(
+                eq(CommandsInterface.CF_ACTION_ERASURE),
+                anyInt(),
+                eq(null),
+                anyInt(),
+                eq(carrierConfigTime),
+                any());
+    }
 }
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 8ee3a37..110dade 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
@@ -1146,6 +1146,8 @@
         mSatelliteController1.countOfDemoModeIncomingDatagramFail = 2;
         mSatelliteController1.countOfDatagramTypeKeepAliveSuccess = 1;
         mSatelliteController1.countOfDatagramTypeKeepAliveFail = 2;
+        mSatelliteController1.isProvisioned = true;
+        mSatelliteController1.carrierId = 1;
 
         mSatelliteController2 = new SatelliteController();
         mSatelliteController2.countOfSatelliteServiceEnablementsSuccess = 2 + 1;
@@ -1173,6 +1175,8 @@
         mSatelliteController2.countOfDemoModeIncomingDatagramFail = 3;
         mSatelliteController2.countOfDatagramTypeKeepAliveSuccess = 4;
         mSatelliteController2.countOfDatagramTypeKeepAliveFail = 5;
+        mSatelliteController2.isProvisioned = false;
+        mSatelliteController2.carrierId = 10;
 
         // SatelliteController atom has one data point
         mSatelliteControllers =
@@ -1182,12 +1186,12 @@
 
         mSatelliteSession1 = new SatelliteSession();
         mSatelliteSession1.satelliteServiceInitializationResult =
-                SatelliteProtoEnums.SATELLITE_ERROR_NONE;
+                SatelliteProtoEnums.SATELLITE_RESULT_SUCCESS;
         mSatelliteSession1.satelliteTechnology =
                 SatelliteProtoEnums.NT_RADIO_TECHNOLOGY_PROPRIETARY;
         mSatelliteSession1.count = 1;
         mSatelliteSession1.satelliteServiceTerminationResult =
-                SatelliteProtoEnums.SATELLITE_ERROR_NONE;
+                SatelliteProtoEnums.SATELLITE_RESULT_SUCCESS;
         mSatelliteSession1.initializationProcessingTimeMillis = 100;
         mSatelliteSession1.terminationProcessingTimeMillis = 200;
         mSatelliteSession1.sessionDurationSeconds = 3;
@@ -1197,15 +1201,19 @@
         mSatelliteSession1.countOfIncomingDatagramFailed = 0;
         mSatelliteSession1.isDemoMode = false;
         mSatelliteSession1.maxNtnSignalStrengthLevel = 2;
+        mSatelliteSession1.carrierId = 2;
+        mSatelliteSession1.countOfSatelliteNotificationDisplayed = 4;
+        mSatelliteSession1.countOfAutoExitDueToScreenOff = 6;
+        mSatelliteSession1.countOfAutoExitDueToTnNetwork = 7;
 
         mSatelliteSession2 = new SatelliteSession();
         mSatelliteSession2.satelliteServiceInitializationResult =
-                SatelliteProtoEnums.SATELLITE_MODEM_ERROR;
+                SatelliteProtoEnums.SATELLITE_RESULT_MODEM_ERROR;
         mSatelliteSession2.satelliteTechnology =
                 SatelliteProtoEnums.NT_RADIO_TECHNOLOGY_NB_IOT_NTN;
         mSatelliteSession2.count = 1;
         mSatelliteSession2.satelliteServiceTerminationResult =
-                SatelliteProtoEnums.SATELLITE_ERROR_NONE;
+                SatelliteProtoEnums.SATELLITE_RESULT_SUCCESS;
         mSatelliteSession2.initializationProcessingTimeMillis = 300;
         mSatelliteSession2.terminationProcessingTimeMillis = 100;
         mSatelliteSession2.sessionDurationSeconds = 10;
@@ -1215,6 +1223,10 @@
         mSatelliteSession2.countOfIncomingDatagramFailed = 1;
         mSatelliteSession2.isDemoMode = true;
         mSatelliteSession2.maxNtnSignalStrengthLevel = 4;
+        mSatelliteSession2.carrierId = 20;
+        mSatelliteSession2.countOfSatelliteNotificationDisplayed = 40;
+        mSatelliteSession2.countOfAutoExitDueToScreenOff = 60;
+        mSatelliteSession2.countOfAutoExitDueToTnNetwork = 70;
 
         mSatelliteSessions =
                 new SatelliteSession[] {
@@ -1222,16 +1234,18 @@
                 };
 
         mSatelliteIncomingDatagram1 = new SatelliteIncomingDatagram();
-        mSatelliteIncomingDatagram1.resultCode = SatelliteProtoEnums.SATELLITE_ERROR_NONE;
+        mSatelliteIncomingDatagram1.resultCode = SatelliteProtoEnums.SATELLITE_RESULT_SUCCESS;
         mSatelliteIncomingDatagram1.datagramSizeBytes = 1 * 1024;
         mSatelliteIncomingDatagram1.datagramTransferTimeMillis = 3 * 1000;
         mSatelliteIncomingDatagram1.isDemoMode = false;
+        mSatelliteIncomingDatagram1.carrierId = 1;
 
         mSatelliteIncomingDatagram2 = new SatelliteIncomingDatagram();
-        mSatelliteIncomingDatagram2.resultCode = SatelliteProtoEnums.SATELLITE_MODEM_ERROR;
+        mSatelliteIncomingDatagram2.resultCode = SatelliteProtoEnums.SATELLITE_RESULT_MODEM_ERROR;
         mSatelliteIncomingDatagram2.datagramSizeBytes = 512;
         mSatelliteIncomingDatagram2.datagramTransferTimeMillis = 1 * 1000;
-        mSatelliteIncomingDatagram1.isDemoMode = true;
+        mSatelliteIncomingDatagram2.isDemoMode = true;
+        mSatelliteIncomingDatagram2.carrierId = 10;
 
         mSatelliteIncomingDatagrams =
                 new SatelliteIncomingDatagram[] {
@@ -1241,18 +1255,20 @@
         mSatelliteOutgoingDatagram1 = new SatelliteOutgoingDatagram();
         mSatelliteOutgoingDatagram1.datagramType =
                 SatelliteProtoEnums.DATAGRAM_TYPE_LOCATION_SHARING;
-        mSatelliteOutgoingDatagram1.resultCode = SatelliteProtoEnums.SATELLITE_ERROR_NONE;
+        mSatelliteOutgoingDatagram1.resultCode = SatelliteProtoEnums.SATELLITE_RESULT_SUCCESS;
         mSatelliteOutgoingDatagram1.datagramSizeBytes = 1 * 1024;
         mSatelliteOutgoingDatagram1.datagramTransferTimeMillis = 3 * 1000;
         mSatelliteOutgoingDatagram1.isDemoMode = false;
+        mSatelliteOutgoingDatagram1.carrierId = 1;
 
         mSatelliteOutgoingDatagram2 = new SatelliteOutgoingDatagram();
         mSatelliteOutgoingDatagram2.datagramType =
                 SatelliteProtoEnums.DATAGRAM_TYPE_SOS_MESSAGE;
-        mSatelliteOutgoingDatagram2.resultCode = SatelliteProtoEnums.SATELLITE_MODEM_ERROR;
+        mSatelliteOutgoingDatagram2.resultCode = SatelliteProtoEnums.SATELLITE_RESULT_MODEM_ERROR;
         mSatelliteOutgoingDatagram2.datagramSizeBytes = 512;
         mSatelliteOutgoingDatagram2.datagramTransferTimeMillis = 1 * 1000;
-        mSatelliteOutgoingDatagram1.isDemoMode = true;
+        mSatelliteOutgoingDatagram2.isDemoMode = true;
+        mSatelliteOutgoingDatagram2.carrierId = 10;
 
         mSatelliteOutgoingDatagrams =
                 new SatelliteOutgoingDatagram[] {
@@ -1260,17 +1276,19 @@
                 };
 
         mSatelliteProvision1 = new SatelliteProvision();
-        mSatelliteProvision1.resultCode = SatelliteProtoEnums.SATELLITE_ERROR_NONE;
+        mSatelliteProvision1.resultCode = SatelliteProtoEnums.SATELLITE_RESULT_SUCCESS;
         mSatelliteProvision1.provisioningTimeSec = 3 * 60;
         mSatelliteProvision1.isProvisionRequest = true;
         mSatelliteProvision1.isCanceled = false;
+        mSatelliteProvision1.carrierId = 1;
 
         mSatelliteProvision2 = new SatelliteProvision();
         mSatelliteProvision2.resultCode =
-                SatelliteProtoEnums.SATELLITE_SERVICE_NOT_PROVISIONED;
+                SatelliteProtoEnums.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED;
         mSatelliteProvision2.provisioningTimeSec = 0;
         mSatelliteProvision2.isProvisionRequest = false;
         mSatelliteProvision2.isCanceled = true;
+        mSatelliteProvision2.carrierId = 10;
 
         mSatelliteProvisions =
                 new SatelliteProvision[] {
@@ -1286,6 +1304,8 @@
         mSatelliteSosMessageRecommender1.isMultiSim = true;
         mSatelliteSosMessageRecommender1.recommendingHandoverType = 1;
         mSatelliteSosMessageRecommender1.isSatelliteAllowedInCurrentLocation = true;
+        mSatelliteSosMessageRecommender1.isWifiConnected = true;
+        mSatelliteSosMessageRecommender1.carrierId = 1;
         mSatelliteSosMessageRecommender1.count = 1;
 
         mSatelliteSosMessageRecommender2 = new SatelliteSosMessageRecommender();
@@ -1297,6 +1317,9 @@
         mSatelliteSosMessageRecommender2.isMultiSim = false;
         mSatelliteSosMessageRecommender2.recommendingHandoverType = 0;
         mSatelliteSosMessageRecommender2.isSatelliteAllowedInCurrentLocation = true;
+        mSatelliteSosMessageRecommender2.isWifiConnected = false;
+        mSatelliteSosMessageRecommender2.carrierId = 2;
+
         mSatelliteSosMessageRecommender2.count = 1;
 
         mSatelliteSosMessageRecommenders =
@@ -1352,6 +1375,8 @@
         mCarrierRoamingSatelliteControllerStats1.satelliteSessionGapMinSec = 2;
         mCarrierRoamingSatelliteControllerStats1.satelliteSessionGapAvgSec = 3;
         mCarrierRoamingSatelliteControllerStats1.satelliteSessionGapMaxSec = 4;
+        mCarrierRoamingSatelliteControllerStats1.carrierId = 1;
+        mCarrierRoamingSatelliteControllerStats1.isDeviceEntitled = true;
 
         mCarrierRoamingSatelliteControllerStats2 = new CarrierRoamingSatelliteControllerStats();
         mCarrierRoamingSatelliteControllerStats2.configDataSource =
@@ -1362,6 +1387,8 @@
         mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapMinSec = 5;
         mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapAvgSec = 10;
         mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapMaxSec = 15;
+        mCarrierRoamingSatelliteControllerStats2.carrierId = 10;
+        mCarrierRoamingSatelliteControllerStats2.isDeviceEntitled = false;
 
         // CarrierRoamingSatelliteController has one data point
         mCarrierRoamingSatelliteControllerStats = new CarrierRoamingSatelliteControllerStats[] {
@@ -1380,7 +1407,7 @@
         mSatelliteEntitlement2.result = 1;
         mSatelliteEntitlement2.entitlementStatus =
                 SatelliteProtoEnums.SATELLITE_ENTITLEMENT_STATUS_DISABLED;
-        mSatelliteEntitlement1.isRetry = true;
+        mSatelliteEntitlement2.isRetry = true;
         mSatelliteEntitlement2.count = 1;
 
         mSatelliteEntitlements = new SatelliteEntitlement[] {mSatelliteEntitlement1,
@@ -1414,10 +1441,11 @@
         mSatelliteAccessController1.resultCode = SATELLITE_RESULT_SUCCESS;
         mSatelliteAccessController1.countryCodes = new String[]{"AB", "CD"};
         mSatelliteAccessController1.configDataSource = CONFIG_DATA_SOURCE_DEVICE_CONFIG;
+        mSatelliteAccessController1.carrierId = 1;
 
         mSatelliteAccessController2 = new SatelliteAccessController();
-        mSatelliteAccessController1.accessControlType = ACCESS_CONTROL_TYPE_CURRENT_LOCATION;
-        mSatelliteAccessController1.locationQueryTimeMillis = TimeUnit.SECONDS.toMillis(4);
+        mSatelliteAccessController2.accessControlType = ACCESS_CONTROL_TYPE_CURRENT_LOCATION;
+        mSatelliteAccessController2.locationQueryTimeMillis = TimeUnit.SECONDS.toMillis(4);
         mSatelliteAccessController2.onDeviceLookupTimeMillis = TimeUnit.SECONDS.toMillis(5);
         mSatelliteAccessController2.totalCheckingTimeMillis = TimeUnit.SECONDS.toMillis(6);
         mSatelliteAccessController2.isAllowed = false;
@@ -1425,6 +1453,7 @@
         mSatelliteAccessController2.resultCode = SATELLITE_RESULT_SUCCESS;
         mSatelliteAccessController2.countryCodes = new String[]{"EF", "GH"};
         mSatelliteAccessController2.configDataSource = CONFIG_DATA_SOURCE_CONFIG_UPDATER;
+        mSatelliteAccessController2.carrierId = 10;
 
         mSatelliteAccessControllers = new SatelliteAccessController[]{
                 mSatelliteAccessController1, mSatelliteAccessController2
@@ -4421,6 +4450,8 @@
         expected.countOfDatagramTypeKeepAliveFail =
                 mSatelliteController1.countOfDatagramTypeKeepAliveFail
                         + mSatelliteController2.countOfDatagramTypeKeepAliveFail;
+        expected.isProvisioned = mSatelliteController2.isProvisioned;
+        expected.carrierId = mSatelliteController2.carrierId;
 
         // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
@@ -4943,41 +4974,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;
-
+                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
@@ -5825,6 +5871,18 @@
                 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);
     }
 
     private static void assertHasStatsAndCount(
@@ -5851,7 +5909,14 @@
                         == expectedStats.countOfIncomingDatagramSuccess
                     && stats.countOfIncomingDatagramFailed
                         == expectedStats.countOfIncomingDatagramFailed
-                    && stats.isDemoMode == expectedStats.isDemoMode) {
+                    && stats.isDemoMode == expectedStats.isDemoMode
+                    && stats.carrierId == expectedStats.carrierId
+                    && stats.countOfSatelliteNotificationDisplayed
+                    == expectedStats.countOfSatelliteNotificationDisplayed
+                    && stats.countOfAutoExitDueToScreenOff
+                    == expectedStats.countOfAutoExitDueToScreenOff
+                    && stats.countOfAutoExitDueToTnNetwork
+                    == expectedStats.countOfAutoExitDueToTnNetwork) {
                 actualCount = stats.count;
             }
         }
@@ -5868,7 +5933,8 @@
                     && stats.datagramSizeBytes == expectedStats.datagramSizeBytes
                     && stats.datagramTransferTimeMillis
                         == expectedStats.datagramTransferTimeMillis
-                    && stats.isDemoMode == expectedStats.isDemoMode) {
+                    && stats.isDemoMode == expectedStats.isDemoMode
+                    && stats.carrierId == expectedStats.carrierId) {
                 actualCount++;
             }
         }
@@ -5886,7 +5952,8 @@
                     && stats.datagramSizeBytes == expectedStats.datagramSizeBytes
                     && stats.datagramTransferTimeMillis
                         == expectedStats.datagramTransferTimeMillis
-                    && stats.isDemoMode == expectedStats.isDemoMode) {
+                    && stats.isDemoMode == expectedStats.isDemoMode
+                    && stats.carrierId == expectedStats.carrierId) {
                 actualCount++;
             }
         }
@@ -5902,7 +5969,8 @@
             if (stats.resultCode == expectedStats.resultCode
                     && stats.provisioningTimeSec == expectedStats.provisioningTimeSec
                     && stats.isProvisionRequest == expectedStats.isProvisionRequest
-                    && stats.isCanceled == expectedStats.isCanceled) {
+                    && stats.isCanceled == expectedStats.isCanceled
+                    && stats.carrierId == expectedStats.carrierId) {
                 actualCount++;
             }
         }
@@ -5923,7 +5991,9 @@
                     && stats.isMultiSim == expectedStats.isMultiSim
                     && stats.recommendingHandoverType == expectedStats.recommendingHandoverType
                     && stats.isSatelliteAllowedInCurrentLocation
-                    == expectedStats.isSatelliteAllowedInCurrentLocation) {
+                    == expectedStats.isSatelliteAllowedInCurrentLocation
+                    && stats.isWifiConnected == expectedStats.isWifiConnected
+                    && stats.carrierId == expectedStats.carrierId) {
                 actualCount = stats.count;
             }
         }
@@ -5945,7 +6015,8 @@
                     && stats.isEmergency == expectedStats.isEmergency
                     && stats.resultCode == expectedStats.resultCode
                     && Arrays.equals(stats.countryCodes, expectedStats.countryCodes)
-                    && stats.configDataSource == expectedStats.configDataSource) {
+                    && stats.configDataSource == expectedStats.configDataSource
+                    && stats.carrierId == expectedStats.carrierId) {
                 actualCount++;
             }
         }
@@ -6193,18 +6264,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);
+        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 cda96ef..5740336 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
@@ -23,6 +23,7 @@
 import static com.android.internal.telephony.satellite.SatelliteConstants.ACCESS_CONTROL_TYPE_CACHED_COUNTRY_CODE;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
@@ -102,6 +103,10 @@
                         .setCountOfDemoModeIncomingDatagramFail(2)
                         .setCountOfDatagramTypeKeepAliveSuccess(1)
                         .setCountOfDatagramTypeKeepAliveFail(2)
+                        .setCountOfAllowedSatelliteAccess(1)
+                        .setCountOfDisallowedSatelliteAccess(2)
+                        .setCountOfSatelliteAccessCheckFail(3)
+                        .setIsProvisioned(true)
                         .build();
 
         mSatelliteStats.onSatelliteControllerMetrics(param);
@@ -160,6 +165,59 @@
                 stats.countOfDatagramTypeKeepAliveSuccess);
         assertEquals(param.getCountOfDatagramTypeKeepAliveFail(),
                 stats.countOfDatagramTypeKeepAliveFail);
+        assertEquals(param.getCountOfAllowedSatelliteAccess(), stats.countOfAllowedSatelliteAccess);
+        assertEquals(param.getCountOfDisallowedSatelliteAccess(),
+                stats.countOfDisallowedSatelliteAccess);
+        assertEquals(param.getCountOfSatelliteAccessCheckFail(),
+                stats.countOfSatelliteAccessCheckFail);
+        assertEquals(param.isProvisioned(), stats.isProvisioned);
+
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onSatelliteControllerMetrics_isProvisioned() throws Exception {
+        SatelliteStats.SatelliteControllerParams param =
+                new SatelliteStats.SatelliteControllerParams.Builder()
+                        .setCountOfSatelliteServiceEnablementsSuccess(2)
+                        .setIsProvisioned(true)
+                        .build();
+        mSatelliteStats.onSatelliteControllerMetrics(param);
+
+        ArgumentCaptor<SatelliteController> captor =
+                ArgumentCaptor.forClass(SatelliteController.class);
+        verify(mPersistAtomsStorage, times(1)).addSatelliteControllerStats(captor.capture());
+        SatelliteController stats = captor.getValue();
+        assertEquals(param.getCountOfSatelliteServiceEnablementsSuccess(),
+                stats.countOfSatelliteServiceEnablementsSuccess);
+        assertEquals(param.isProvisioned(), stats.isProvisioned);
+
+        param = new SatelliteStats.SatelliteControllerParams.Builder()
+                .setCountOfSatelliteServiceEnablementsSuccess(2)
+                .build();
+        mSatelliteStats.onSatelliteControllerMetrics(param);
+
+        captor = ArgumentCaptor.forClass(SatelliteController.class);
+        verify(mPersistAtomsStorage, times(2)).addSatelliteControllerStats(captor.capture());
+        stats = captor.getValue();
+        // count should be added
+        assertEquals(2, stats.countOfSatelliteServiceEnablementsSuccess);
+        // isProvisioned value should not be updated
+        assertEquals(true, stats.isProvisioned);
+
+        param = new SatelliteStats.SatelliteControllerParams.Builder()
+                .setCountOfSatelliteServiceEnablementsSuccess(2)
+                .setIsProvisioned(false)
+                .build();
+        mSatelliteStats.onSatelliteControllerMetrics(param);
+
+        captor = ArgumentCaptor.forClass(SatelliteController.class);
+        verify(mPersistAtomsStorage, times(3)).addSatelliteControllerStats(captor.capture());
+        stats = captor.getValue();
+        // count should be added
+        assertEquals(2, stats.countOfSatelliteServiceEnablementsSuccess);
+        // isProvisioned should be updated
+        assertEquals(false, stats.isProvisioned);
 
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
@@ -169,9 +227,9 @@
         SatelliteStats.SatelliteSessionParams param =
                 new SatelliteStats.SatelliteSessionParams.Builder()
                         .setSatelliteServiceInitializationResult(
-                                SatelliteProtoEnums.SATELLITE_ERROR_NONE)
+                                SatelliteProtoEnums.SATELLITE_RESULT_SUCCESS)
                         .setSatelliteTechnology(SatelliteProtoEnums.NT_RADIO_TECHNOLOGY_PROPRIETARY)
-                        .setTerminationResult(SatelliteProtoEnums.SATELLITE_ERROR_NONE)
+                        .setTerminationResult(SatelliteProtoEnums.SATELLITE_RESULT_SUCCESS)
                         .setInitializationProcessingTime(100)
                         .setTerminationProcessingTime(200)
                         .setSessionDuration(3)
@@ -212,7 +270,7 @@
     public void onSatelliteIncomingDatagramMetrics_withAtoms() throws Exception {
         SatelliteStats.SatelliteIncomingDatagramParams param =
                 new SatelliteStats.SatelliteIncomingDatagramParams.Builder()
-                        .setResultCode(SatelliteProtoEnums.SATELLITE_ERROR_NONE)
+                        .setResultCode(SatelliteProtoEnums.SATELLITE_RESULT_SUCCESS)
                         .setDatagramSizeBytes(1 * 1024)
                         .setDatagramTransferTimeMillis(3 * 1000)
                         .setIsDemoMode(true)
@@ -236,7 +294,7 @@
         SatelliteStats.SatelliteOutgoingDatagramParams param =
                 new SatelliteStats.SatelliteOutgoingDatagramParams.Builder()
                         .setDatagramType(SatelliteProtoEnums.DATAGRAM_TYPE_LOCATION_SHARING)
-                        .setResultCode(SatelliteProtoEnums.SATELLITE_ERROR_NONE)
+                        .setResultCode(SatelliteProtoEnums.SATELLITE_RESULT_SUCCESS)
                         .setDatagramSizeBytes(1 * 1024)
                         .setDatagramTransferTimeMillis(3 * 1000)
                         .setIsDemoMode(true)
@@ -261,7 +319,7 @@
         SatelliteStats.SatelliteProvisionParams param =
                 new SatelliteStats.SatelliteProvisionParams.Builder()
                         .setResultCode(
-                                SatelliteProtoEnums.SATELLITE_SERVICE_PROVISION_IN_PROGRESS)
+                                SatelliteProtoEnums.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS)
                         .setProvisioningTimeSec(5 * 1000)
                         .setIsProvisionRequest(true)
                         .setIsCanceled(false)
@@ -292,6 +350,8 @@
                         .setIsMultiSim(false)
                         .setRecommendingHandoverType(0)
                         .setIsSatelliteAllowedInCurrentLocation(true)
+                        .setIsWifiConnected(true)
+                        .setCarrierId(1)
                         .build();
 
         mSatelliteStats.onSatelliteSosMessageRecommender(param);
@@ -309,6 +369,8 @@
         assertEquals(param.getRecommendingHandoverType(), stats.recommendingHandoverType);
         assertEquals(param.isSatelliteAllowedInCurrentLocation(),
                 stats.isSatelliteAllowedInCurrentLocation);
+        assertEquals(param.isWifiConnected(), stats.isWifiConnected);
+        assertEquals(param.getCarrierId(), stats.carrierId);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -372,6 +434,8 @@
                         .setSatelliteSessionGapMinSec(15)
                         .setSatelliteSessionGapAvgSec(30)
                         .setSatelliteSessionGapMaxSec(45)
+                        .setCarrierId(10)
+                        .setIsDeviceEntitled(true)
                         .build();
 
         mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(param);
@@ -390,6 +454,70 @@
         assertEquals(param.getSatelliteSessionGapMinSec(), stats.satelliteSessionGapMinSec);
         assertEquals(param.getSatelliteSessionGapAvgSec(), stats.satelliteSessionGapAvgSec);
         assertEquals(param.getSatelliteSessionGapMaxSec(), stats.satelliteSessionGapMaxSec);
+        assertEquals(param.getCarrierId(), stats.carrierId);
+        assertEquals(param.isDeviceEntitled(), stats.isDeviceEntitled);
+
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onCarrierRoamingSatelliteControllerStatsMetrics_testStaticFields()
+            throws Exception {
+        SatelliteStats.CarrierRoamingSatelliteControllerStatsParams param =
+                new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                        .setConfigDataSource(4)
+                        .setCountOfEntitlementStatusQueryRequest(6)
+                        .setCountOfSatelliteConfigUpdateRequest(2)
+                        .setCountOfSatelliteNotificationDisplayed(1)
+                        .setSatelliteSessionGapMinSec(15)
+                        .setSatelliteSessionGapAvgSec(30)
+                        .setSatelliteSessionGapMaxSec(45)
+                        .setCarrierId(10)
+                        .setIsDeviceEntitled(true)
+                        .build();
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(param);
+
+        ArgumentCaptor<CarrierRoamingSatelliteControllerStats> captor =
+                ArgumentCaptor.forClass(CarrierRoamingSatelliteControllerStats.class);
+        verify(mPersistAtomsStorage, times(1)).addCarrierRoamingSatelliteControllerStats(
+                captor.capture());
+        CarrierRoamingSatelliteControllerStats stats = captor.getValue();
+        assertEquals(param.getCountOfEntitlementStatusQueryRequest(),
+                stats.countOfEntitlementStatusQueryRequest);
+        assertEquals(param.getCarrierId(), stats.carrierId);
+        assertEquals(param.isDeviceEntitled(), stats.isDeviceEntitled);
+
+        param = new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                .setCountOfSatelliteConfigUpdateRequest(2)
+                .build();
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(param);
+
+        captor = ArgumentCaptor.forClass(CarrierRoamingSatelliteControllerStats.class);
+        verify(mPersistAtomsStorage, times(2)).addCarrierRoamingSatelliteControllerStats(
+                captor.capture());
+        stats = captor.getValue();
+        // count should be added
+        assertEquals(2, stats.countOfSatelliteConfigUpdateRequest);
+        // static values should not be updated
+        assertEquals(10, stats.carrierId);
+        assertEquals(true, stats.isDeviceEntitled);
+
+        param = new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                .setCountOfSatelliteConfigUpdateRequest(2)
+                .setCarrierId(20)
+                .setIsDeviceEntitled(false)
+                .build();
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(param);
+
+        captor = ArgumentCaptor.forClass(CarrierRoamingSatelliteControllerStats.class);
+        verify(mPersistAtomsStorage, times(3)).addCarrierRoamingSatelliteControllerStats(
+                captor.capture());
+        stats = captor.getValue();
+        // count should be added
+        assertEquals(2, stats.countOfSatelliteConfigUpdateRequest);
+        // static values should be updated
+        assertEquals(20, stats.carrierId);
+        assertEquals(false, stats.isDeviceEntitled);
 
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
index 04d140c..4347869 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
@@ -30,6 +30,10 @@
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_SUPER_WIDEBAND;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_DISCONNECTED;
+import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_UNKNOWN;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -268,6 +272,8 @@
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_SUPER_WIDEBAND;
         expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
         expectedCall.callDuration = VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_UNKNOWN;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
@@ -319,6 +325,8 @@
         expectedCall.setupDurationMillis = 200;
         expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
         expectedCall.callDuration = VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_UNKNOWN;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_DISCONNECTED;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 2200L, 1L);
@@ -363,6 +371,8 @@
         expectedCall.setupDurationMillis = 200;
         expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
         expectedCall.callDuration = VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_UNKNOWN;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_DISCONNECTED;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 2200L, 1L);
@@ -411,6 +421,8 @@
         expectedCall.disconnectExtraMessage = "normal call clearing";
         expectedCall.callDuration =
                 VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_ONE_MINUTE;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 100000L, 1L);
@@ -569,6 +581,8 @@
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.callDuration =
                 VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_FIVE_MINUTES;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
@@ -628,6 +642,8 @@
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.isMultiSim = false; // DSDS with one active SIM profile should not count
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_UNKNOWN;
 
         mVoiceCallSessionStats0.setTimeMillis(2000L);
         doReturn(Call.State.INCOMING).when(mImsCall0).getState();
@@ -860,6 +876,8 @@
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.callDuration =
                 VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_TEN_MINUTES;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
 
         mVoiceCallSessionStats0.setTimeMillis(2000L);
         doReturn(Call.State.INCOMING).when(mImsCall0).getState();
@@ -918,6 +936,8 @@
         expectedCall.callDuration =
                 VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_THIRTY_MINUTES;
         expectedCall.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsageLte =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 4000L, 1L);
@@ -996,6 +1016,8 @@
         expectedCall.callDuration =
                 VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_THIRTY_MINUTES;
         expectedCall.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsageLte =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 4000L, 1L);
@@ -1130,7 +1152,8 @@
         expectedCall.rttEnabled = true;
         expectedCall.callDuration =
                 VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_MORE_THAN_ONE_HOUR;
-
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         mVoiceCallSessionStats0.setTimeMillis(2000L);
         doReturn(Call.State.INCOMING).when(mImsCall0).getState();
         doReturn(Call.State.INCOMING).when(mImsConnection0).getState();
@@ -1187,6 +1210,8 @@
         expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_HSPA;
         expectedCall0.bandAtEnd = 0;
         expectedCall0.lastKnownRat = TelephonyManager.NETWORK_TYPE_HSPA;
+        expectedCall0.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
         // call 1 starts later, MT
         doReturn(true).when(mImsConnection1).isIncoming();
         doReturn(60000L).when(mImsConnection1).getCreateTime();
@@ -1210,6 +1235,8 @@
         expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
         expectedCall1.bandAtEnd = 0;
         expectedCall1.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall1.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsageLte =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 80000L, 2L);
@@ -1319,6 +1346,8 @@
         expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_UMTS;
         expectedCall0.bandAtEnd = 0;
         expectedCall0.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall0.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
         // call 1 starts later, MT
         doReturn(true).when(mImsConnection1).isIncoming();
         doReturn(60000L).when(mImsConnection1).getCreateTime();
@@ -1342,6 +1371,8 @@
         expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_HSPA;
         expectedCall1.bandAtEnd = 0;
         expectedCall1.lastKnownRat = TelephonyManager.NETWORK_TYPE_HSPA;
+        expectedCall1.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsageLte =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 80000L, 2L);
@@ -1449,6 +1480,8 @@
         expectedCall0.ratSwitchCount = 0L;
         expectedCall0.ratSwitchCountAfterConnected = 0L;
         expectedCall0.ratAtEnd = TelephonyManager.NETWORK_TYPE_LTE;
+        expectedCall0.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
         // call 1 starts later, MT
         doReturn(true).when(mImsConnection1).isIncoming();
         doReturn(60000L).when(mImsConnection1).getCreateTime();
@@ -1472,6 +1505,8 @@
         expectedCall1.ratAtEnd = TelephonyManager.NETWORK_TYPE_HSPA;
         expectedCall1.bandAtEnd = 0;
         expectedCall1.lastKnownRat = TelephonyManager.NETWORK_TYPE_HSPA;
+        expectedCall1.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsageLte =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 80000L, 2L);
@@ -1571,6 +1606,8 @@
         expectedCall.disconnectExtraMessage = "normal call clearing";
         expectedCall.callDuration =
                 VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_LESS_THAN_ONE_MINUTE;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_IWLAN, 2000L, 100000L, 1L);
@@ -1631,6 +1668,8 @@
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
         VoiceCallRatUsage expectedRatUsageLte =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 3000L, 1L);
@@ -1695,6 +1734,8 @@
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_UNKNOWN;
         expectedCall.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_DISCONNECTED;
         VoiceCallRatUsage expectedRatUsageLte =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 3000L, 1L);
@@ -1753,6 +1794,8 @@
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
         VoiceCallRatUsage expectedRatUsageLte =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 3000L, 1L);
@@ -1818,6 +1861,8 @@
                 (1L << AudioCodec.AUDIO_CODEC_AMR) | (1L << AudioCodec.AUDIO_CODEC_AMR_WB);
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_UNKNOWN;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_UMTS, 2500L, 15000L, 1L);
@@ -1871,6 +1916,8 @@
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.bandAtEnd = 0;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_UMTS, 2500L, 100000L, 1L);
@@ -1934,6 +1981,8 @@
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsageLte =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 10000L, 1L);
@@ -2009,6 +2058,8 @@
         expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
@@ -2087,6 +2138,8 @@
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsageLte =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 7000L, 1L);
@@ -2185,6 +2238,8 @@
         expectedCall0.srvccCompleted = true;
         expectedCall0.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
         expectedCall0.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall0.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
         // call 1 starts later, MT
         doReturn(true).when(mImsConnection1).isIncoming();
         doReturn(60000L).when(mImsConnection1).getCreateTime();
@@ -2213,6 +2268,8 @@
         expectedCall1.srvccCompleted = true;
         expectedCall1.bearerAtEnd = VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
         expectedCall1.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
+        expectedCall1.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsageLte =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 80000L, 2L);
@@ -2312,6 +2369,8 @@
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.lastKnownRat = TelephonyManager.NETWORK_TYPE_UMTS;
         expectedCall.handoverInProgress = false;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
         VoiceCallRatUsage expectedRatUsageLte =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 3000L, 1L);
@@ -2386,6 +2445,8 @@
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.handoverInProgress = true;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
@@ -2440,6 +2501,8 @@
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.handoverInProgress = true;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
@@ -2498,6 +2561,8 @@
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.handoverInProgress = false;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
@@ -2562,6 +2627,8 @@
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.isEmergency = true;
         expectedCall.handoverInProgress = true;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ACTIVE;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
@@ -2627,6 +2694,8 @@
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
         expectedCall.bandAtEnd = 0;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_UNKNOWN;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_IWLAN, 2000L, 8000L, 1L);
@@ -2677,6 +2746,8 @@
         expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_AMR;
         expectedCall.mainCodecQuality =
                 VOICE_CALL_SESSION__MAIN_CODEC_QUALITY__CODEC_QUALITY_NARROWBAND;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_UNKNOWN;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_IWLAN, 2000L, 8000L, 1L);
@@ -2730,6 +2801,8 @@
         expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
         expectedCall.callDuration = VOICE_CALL_SESSION__CALL_DURATION__CALL_DURATION_UNKNOWN;
         expectedCall.vonrEnabled = true;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
@@ -2826,6 +2899,8 @@
         expectedCall.supportsBusinessCallComposer = true;
         // 0 is defined as UNKNOWN, adding 1 to original value.
         expectedCall.callComposerStatus = 3;
+        expectedCall.preciseCallStateOnSetup =
+                VOICE_CALL_SESSION__CALL_STATE_ON_SETUP__CALL_STATE_ALERTING;
         VoiceCallRatUsage expectedRatUsage =
                 makeRatUsageProto(
                         CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
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..21731f0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramControllerTest.java
@@ -92,8 +92,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);
     }
 
@@ -197,7 +197,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 +219,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 7094399..d964d88 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
@@ -16,9 +16,11 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
+import static com.android.internal.telephony.SmsDispatchersController.PendingRequest;
 import static com.android.internal.telephony.satellite.DatagramController.SATELLITE_ALIGN_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -47,10 +49,10 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.os.AsyncResult;
+import android.os.Binder;
 import android.os.Looper;
 import android.os.Message;
 import android.telephony.Rlog;
-import android.telephony.SubscriptionManager;
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 import android.testing.AndroidTestingRunner;
@@ -59,6 +61,7 @@
 import com.android.internal.R;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.SmsDispatchersController;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
@@ -91,6 +94,7 @@
             SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP;
     private static final int DATAGRAM_TYPE5 =
             SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED;
+    private static final int DATAGRAM_TYPE6 = SatelliteManager.DATAGRAM_TYPE_SMS;
 
     private static final String TEST_MESSAGE = "This is a test datagram message";
     private static final long TEST_EXPIRE_TIMER_SATELLITE_ALIGN = TimeUnit.SECONDS.toMillis(1);
@@ -108,17 +112,20 @@
 
     private TestDatagramDispatcher mDatagramDispatcherUT;
 
+    @Mock private SatelliteController mMockSatelliteController;
     @Mock private DatagramController mMockDatagramController;
     @Mock private DatagramReceiver mMockDatagramReceiver;
     @Mock private SatelliteModemInterface mMockSatelliteModemInterface;
     @Mock private ControllerMetricsStats mMockControllerMetricsStats;
     @Mock private SatelliteSessionController mMockSatelliteSessionController;
     @Mock private SessionMetricsStats mMockSessionMetricsStats;
+    @Mock private SmsDispatchersController mMockSmsDispatchersController;
 
     /** Variables required to send datagram in the unit tests. */
     LinkedBlockingQueue<Integer> mResultListener;
     SatelliteDatagram mDatagram;
     InOrder mInOrder;
+    private PendingRequest mPendingSms;
 
     private static final long TIMEOUT = 500;
     private List<Integer> mIntegerConsumerResult = new ArrayList<>();
@@ -142,6 +149,8 @@
         MockitoAnnotations.initMocks(this);
         logd(TAG + " Setup!");
 
+        replaceInstance(SatelliteController.class, "sInstance", null,
+                mMockSatelliteController);
         replaceInstance(DatagramController.class, "sInstance", null,
                 mMockDatagramController);
         replaceInstance(DatagramReceiver.class, "sInstance", null,
@@ -157,6 +166,7 @@
 
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
         when(mFeatureFlags.satellitePersistentLogging()).thenReturn(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
         mDatagramDispatcherUT = new TestDatagramDispatcher(mContext, Looper.myLooper(),
                 mFeatureFlags,
                 mMockDatagramController);
@@ -165,6 +175,10 @@
         mDatagram = new SatelliteDatagram(TEST_MESSAGE.getBytes());
         mInOrder = inOrder(mMockDatagramController);
         when(mMockDatagramController.isPollingInIdleState()).thenReturn(true);
+        when(mMockSatelliteController.getSatellitePhone()).thenReturn(mPhone);
+        when(mPhone.getSmsDispatchersController()).thenReturn(mMockSmsDispatchersController);
+        when(mMockSatelliteController.getSatelliteCarrierId()).thenReturn(UNKNOWN_CARRIER_ID);
+        mPendingSms = createPendingRequest();
     }
 
     @After
@@ -175,6 +189,7 @@
         mResultListener = null;
         mDatagram = null;
         mInOrder = null;
+        mPendingSms = null;
         super.tearDown();
     }
 
@@ -269,13 +284,13 @@
             processAllMessages();
             verifyZeroInteractions(mMockSatelliteModemInterface);
             mInOrder.verify(mMockDatagramController)
-                    .updateSendStatus(eq(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
+                    .updateSendStatus(eq(mPhone.getSubId()),
                             eq(datagramType),
                             eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED),
                             eq(1),
                             eq(SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE));
             mInOrder.verify(mMockDatagramController)
-                    .updateSendStatus(eq(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
+                    .updateSendStatus(eq(mPhone.getSubId()),
                             eq(datagramType),
                             eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
                             eq(SATELLITE_RESULT_SUCCESS));
@@ -307,6 +322,9 @@
                     eq(SatelliteServiceUtils.isLastSosMessage(datagramType)));
             assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
             assertEquals(0, mResultListener.size());
+            if (datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+                assertTrue(mDatagramDispatcherUT.isEmergencyCommunicationEstablished());
+            }
 
             mDatagramDispatcherUT.onSatelliteModemStateChanged(
                     SatelliteManager.SATELLITE_MODEM_STATE_OFF);
@@ -318,6 +336,7 @@
             assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
             verify(mMockSessionMetricsStats, times(1))
                     .addCountOfFailedOutgoingDatagram(anyInt(), anyInt());
+            assertFalse(mDatagramDispatcherUT.isEmergencyCommunicationEstablished());
         }
     }
 
@@ -766,6 +785,360 @@
         mDatagramDispatcherUT.setShouldSendDatagramToModemInDemoMode(null);
     }
 
+    @Test
+    public void testSendSms_success() {
+        int datagramType = DATAGRAM_TYPE6;
+        clearInvocations(mMockSmsDispatchersController);
+        clearInvocations(mMockDatagramController);
+        doReturn(true).when(mMockDatagramController).needsWaitingForSatelliteConnected(
+                eq(datagramType));
+        when(mMockDatagramController.getDatagramWaitTimeForConnectedState(eq(false)))
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
+        when(mMockDatagramController.getDatagramWaitTimeForConnectedState(eq(true)))
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_FOR_LAST_MESSAGE_TIMEOUT_MILLIS);
+
+        mDatagramDispatcherUT.sendSms(mPendingSms);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramType));
+        mInOrder.verify(mMockDatagramController).updateSendStatus(eq(SUB_ID), eq(datagramType),
+                eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT),
+                eq(1),
+                eq(SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState(
+                eq(SatelliteServiceUtils.isLastSosMessage(datagramType)));
+        verifyZeroInteractions(mMockSmsDispatchersController);
+        assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+
+        doReturn(false).when(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramType));
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramType));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID), eq(datagramType),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        verify(mMockSmsDispatchersController).sendCarrierRoamingNbIotNtnText(eq(mPendingSms));
+
+        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId, true);
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID), eq(datagramType),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS),
+                        eq(0),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID), eq(datagramType),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        verifyNoMoreInteractions(mMockDatagramController);
+    }
+
+    @Test
+    public void testSendSms_failure() {
+        int datagramType = DATAGRAM_TYPE6;
+        clearInvocations(mMockSmsDispatchersController);
+        clearInvocations(mMockDatagramController);
+        doReturn(false).when(mMockDatagramController).needsWaitingForSatelliteConnected(
+                eq(datagramType));
+        mDatagramDispatcherUT.sendSms(mPendingSms);
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramType));
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(mPhone.getSubId()), eq(datagramType),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        verify(mMockSmsDispatchersController).sendCarrierRoamingNbIotNtnText(eq(mPendingSms));
+
+        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId, false);
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(mPhone.getSubId()),
+                        eq(datagramType),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED),
+                        eq(0),
+                        eq(SatelliteManager.SATELLITE_RESULT_NETWORK_ERROR));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(mPhone.getSubId()),
+                        eq(datagramType),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SATELLITE_RESULT_SUCCESS));
+    }
+
+    @Test
+    public void testSendSms_handleEventDatagramWaitForConnectedStateTimedOut() {
+        int datagramType = DATAGRAM_TYPE6;
+        clearInvocations(mMockSmsDispatchersController);
+        clearInvocations(mMockDatagramController);
+        doReturn(true).when(mMockDatagramController).needsWaitingForSatelliteConnected(
+                eq(datagramType));
+        when(mMockDatagramController.getDatagramWaitTimeForConnectedState(eq(false)))
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
+        when(mMockDatagramController.getDatagramWaitTimeForConnectedState(eq(true)))
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_FOR_LAST_MESSAGE_TIMEOUT_MILLIS);
+
+        mDatagramDispatcherUT.sendSms(mPendingSms);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramType));
+        mInOrder.verify(mMockDatagramController).updateSendStatus(eq(SUB_ID), eq(datagramType),
+                eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT),
+                eq(1),
+                eq(SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState(
+                eq(SatelliteServiceUtils.isLastSosMessage(datagramType)));
+        verifyZeroInteractions(mMockSmsDispatchersController);
+        assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+
+        moveTimeForward(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(mPhone.getSubId()),
+                        eq(datagramType),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED),
+                        eq(1),
+                        eq(SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(mPhone.getSubId()),
+                        eq(datagramType),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        verify(mMockSmsDispatchersController).onSendCarrierRoamingNbIotNtnTextError(
+                eq(mPendingSms), eq(SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE));
+        assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+        verifyZeroInteractions(mMockSmsDispatchersController);
+    }
+
+    @Test
+    public void testSendSms_handleModemStateOff() {
+        int datagramType = DATAGRAM_TYPE6;
+        clearInvocations(mMockSmsDispatchersController);
+        clearInvocations(mMockDatagramController);
+        doReturn(true).when(mMockDatagramController).needsWaitingForSatelliteConnected(
+                eq(datagramType));
+        when(mMockDatagramController.getDatagramWaitTimeForConnectedState(eq(false)))
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
+        when(mMockDatagramController.getDatagramWaitTimeForConnectedState(eq(true)))
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_FOR_LAST_MESSAGE_TIMEOUT_MILLIS);
+
+        mDatagramDispatcherUT.sendSms(mPendingSms);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramType));
+        mInOrder.verify(mMockDatagramController).updateSendStatus(eq(SUB_ID), eq(datagramType),
+                eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT),
+                eq(1),
+                eq(SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState(
+                eq(SatelliteServiceUtils.isLastSosMessage(datagramType)));
+        verifyZeroInteractions(mMockSmsDispatchersController);
+        assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(mPhone.getSubId()),
+                        eq(datagramType),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED),
+                        eq(1),
+                        eq(SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(mPhone.getSubId()),
+                        eq(datagramType),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        verify(mMockSmsDispatchersController).onSendCarrierRoamingNbIotNtnTextError(
+                eq(mPendingSms), eq(SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED));
+        assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+    }
+
+    @Test
+    public void testSendSmsAndDatagram_prioritizeDatagram() {
+        int datagramTypeSms = DATAGRAM_TYPE6;
+        int datagramTypeSos = DATAGRAM_TYPE1;
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[3];
+
+            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                            new AsyncResult(message.obj, null, null))
+                    .sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
+                anyBoolean(), anyBoolean(), any(Message.class));
+
+        clearInvocations(mMockSmsDispatchersController);
+        clearInvocations(mMockDatagramController);
+        clearInvocations(mMockSessionMetricsStats);
+        clearInvocations(mMockSatelliteModemInterface);
+        doReturn(true).when(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramTypeSms));
+        doReturn(true).when(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramTypeSos));
+        when(mMockDatagramController.getDatagramWaitTimeForConnectedState(eq(false)))
+                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
+        when(mMockDatagramController.getDatagramWaitTimeForConnectedState(eq(true)))
+                .thenReturn(
+                        TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_FOR_LAST_MESSAGE_TIMEOUT_MILLIS);
+        mResultListener.clear();
+
+        mDatagramDispatcherUT.sendSms(mPendingSms);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramTypeSms));
+        mInOrder.verify(mMockDatagramController).updateSendStatus(eq(SUB_ID), eq(datagramTypeSms),
+                eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT),
+                eq(1),
+                eq(SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState(
+                eq(SatelliteServiceUtils.isLastSosMessage(datagramTypeSms)));
+        verifyZeroInteractions(mMockSmsDispatchersController);
+        assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, datagramTypeSos, mDatagram,
+                true, mResultListener::offer);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramTypeSos));
+        mInOrder.verify(mMockDatagramController).updateSendStatus(eq(SUB_ID), eq(datagramTypeSos),
+                eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT),
+                eq(2),
+                eq(SATELLITE_RESULT_SUCCESS));
+        verifyZeroInteractions(mMockSatelliteModemInterface);
+        assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+
+        doReturn(false).when(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramTypeSms));
+        doReturn(false).when(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramTypeSos));
+        mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+        // First SOS message is sent, then SMS is sent
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramTypeSos));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID), eq(datagramTypeSos),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(2),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID), eq(datagramTypeSos),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS),
+                        eq(1),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        verify(mMockSessionMetricsStats, times(1))
+                .addCountOfSuccessfulOutgoingDatagram(eq(datagramTypeSos));
+        verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
+                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+        assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+
+        // Send pending SMS
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramTypeSms));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID), eq(datagramTypeSms),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        verify(mMockSmsDispatchersController).sendCarrierRoamingNbIotNtnText(eq(mPendingSms));
+
+        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId, true);
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID), eq(datagramTypeSms),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS),
+                        eq(0),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID), eq(datagramTypeSms),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        verifyNoMoreInteractions(mMockDatagramController);
+    }
+
+    @Test
+    public void testSendSmsAndDatagram_sendingSmsAlreadyInProgress() {
+        int datagramTypeSms = DATAGRAM_TYPE6;
+        int datagramTypeSos = DATAGRAM_TYPE1;
+        doAnswer(invocation -> {
+            Message message = (Message) invocation.getArguments()[3];
+
+            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                            new AsyncResult(message.obj, null, null))
+                    .sendToTarget();
+            return null;
+        }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
+                anyBoolean(), anyBoolean(), any(Message.class));
+
+        clearInvocations(mMockSmsDispatchersController);
+        clearInvocations(mMockDatagramController);
+        doReturn(false).when(mMockDatagramController).needsWaitingForSatelliteConnected(
+                eq(datagramTypeSms));
+        mDatagramDispatcherUT.sendSms(mPendingSms);
+        processAllMessages();
+
+        mInOrder.verify(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramTypeSms));
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(mPhone.getSubId()), eq(datagramTypeSms),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        verify(mMockSmsDispatchersController).sendCarrierRoamingNbIotNtnText(eq(mPendingSms));
+
+        // Sending SMS is already in progress, so datagram should not be sent
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, datagramTypeSos, mDatagram,
+                true, mResultListener::offer);
+        processAllMessages();
+        verifyZeroInteractions(mMockSatelliteModemInterface);
+
+        mDatagramDispatcherUT.onSendSmsDone(mPhone.getSubId(), mPendingSms.uniqueMessageId, true);
+        processAllMessages();
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID), eq(datagramTypeSms),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS),
+                        eq(1),
+                        eq(SATELLITE_RESULT_SUCCESS));
+
+        // Send pending datagram
+        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+        mInOrder.verify(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(datagramTypeSos));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID), eq(datagramTypeSos),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID), eq(datagramTypeSos),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS),
+                        eq(0),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        mInOrder.verify(mMockDatagramController)
+                .updateSendStatus(eq(SUB_ID), eq(datagramTypeSos),
+                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                        eq(SATELLITE_RESULT_SUCCESS));
+        verify(mMockSessionMetricsStats, times(1))
+                .addCountOfSuccessfulOutgoingDatagram(eq(datagramTypeSos));
+        verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
+                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+        assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+    }
+
     private boolean waitForIntegerConsumerResult(int expectedNumberOfEvents) {
         for (int i = 0; i < expectedNumberOfEvents; i++) {
             try {
@@ -819,4 +1192,18 @@
     private static void loge(String message) {
         Rlog.e(TAG, message);
     }
+
+    private static <T> ArrayList<T> asArrayList(T object) {
+        ArrayList<T> list = new ArrayList<>();
+        list.add(object);
+        return list;
+    }
+
+    private PendingRequest createPendingRequest() {
+        return 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, 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 947661b..a1f63d0 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID;
+
 import static com.android.internal.telephony.satellite.DatagramController.SATELLITE_ALIGN_TIMEOUT;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -48,6 +50,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.provider.Telephony;
+import android.telephony.SubscriptionManager;
 import android.telephony.satellite.ISatelliteDatagramCallback;
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
@@ -78,7 +81,7 @@
 @TestableLooper.RunWithLooper
 public class DatagramReceiverTest extends TelephonyTest {
     private static final String TAG = "DatagramReceiverTest";
-    private static final int SUB_ID = 0;
+    private static final int SUB_ID = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
     private static final String TEST_MESSAGE = "This is a test datagram message";
     private static final long TEST_EXPIRE_TIMER_SATELLITE_ALIGN = TimeUnit.SECONDS.toMillis(1);
     private static final long TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS =
@@ -144,6 +147,7 @@
         when(mMockDatagramController.isPollingInIdleState()).thenReturn(true);
         when(mMockDatagramController.needsWaitingForSatelliteConnected(
                 eq(SatelliteManager.DATAGRAM_TYPE_UNKNOWN))).thenReturn(false);
+        when(mMockSatelliteController.getSatelliteCarrierId()).thenReturn(UNKNOWN_CARRIER_ID);
         processAllMessages();
     }
 
@@ -183,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));
@@ -197,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))
@@ -221,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);
@@ -262,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));
 
@@ -285,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));
 
@@ -308,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());
@@ -335,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();
@@ -352,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));
@@ -383,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));
@@ -445,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));
     }
@@ -464,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/DemoSimulatorTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DemoSimulatorTest.java
index 319e39f..2b18468 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DemoSimulatorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DemoSimulatorTest.java
@@ -119,7 +119,7 @@
         moveToNotConnectedState();
 
         verify(mISatelliteListener).onSatelliteModemStateChanged(
-                SatelliteModemState.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+                SatelliteModemState.SATELLITE_MODEM_STATE_OUT_OF_SERVICE);
         ArgumentCaptor<NtnSignalStrength> ntnSignalStrength = ArgumentCaptor.forClass(
                 NtnSignalStrength.class);
         verify(mISatelliteListener).onNtnSignalStrengthChanged(ntnSignalStrength.capture());
@@ -156,9 +156,9 @@
         moveToConnectedState();
 
         verify(mISatelliteListener).onSatelliteModemStateChanged(
-                SatelliteModemState.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+                SatelliteModemState.SATELLITE_MODEM_STATE_OUT_OF_SERVICE);
         verify(mISatelliteListener).onSatelliteModemStateChanged(
-                SatelliteModemState.SATELLITE_MODEM_STATE_CONNECTED);
+                SatelliteModemState.SATELLITE_MODEM_STATE_IN_SERVICE);
         ArgumentCaptor<NtnSignalStrength> ntnSignalStrength = ArgumentCaptor.forClass(
                 NtnSignalStrength.class);
         verify(mISatelliteListener, times(2))
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/PointingAppControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java
index 36d32fe..a228617 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java
@@ -39,6 +39,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.os.UserHandle;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.PointingInfo;
 import android.telephony.satellite.SatelliteManager;
@@ -196,6 +197,11 @@
             }
         }
 
+        @Override
+        public void onSendDatagramRequested(int datagramType) {
+            logd("onSendDatagramRequested: datagramType=" + datagramType);
+        }
+
         public int getDatagramType() {
             return mDatagramType;
         }
@@ -316,7 +322,7 @@
     public void testStartPointingUI() throws Exception {
         ArgumentCaptor<Intent> startedIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         mPointingAppController.startPointingUI(true, true, true);
-        verify(mContext).startActivity(startedIntentCaptor.capture());
+        verify(mContext).startActivityAsUser(startedIntentCaptor.capture(), eq(UserHandle.CURRENT));
         Intent intent = startedIntentCaptor.getValue();
         assertEquals(KEY_POINTING_UI_PACKAGE_NAME, intent.getComponent().getPackageName());
         assertEquals(KEY_POINTING_UI_CLASS_NAME, intent.getComponent().getClassName());
@@ -332,10 +338,12 @@
     @Test
     public void testRestartPointingUi() throws Exception {
         mPointingAppController.startPointingUI(true, false, true);
-        mInOrderForPointingUi.verify(mContext).startActivity(any(Intent.class));
+        mInOrderForPointingUi.verify(mContext).startActivityAsUser(any(Intent.class),
+                eq(UserHandle.CURRENT));
         testRestartPointingUi(true, false, true);
         mPointingAppController.startPointingUI(false, true, false);
-        mInOrderForPointingUi.verify(mContext).startActivity(any(Intent.class));
+        mInOrderForPointingUi.verify(mContext).startActivityAsUser(any(Intent.class),
+                eq(UserHandle.CURRENT));
         testRestartPointingUi(false, true, false);
     }
 
@@ -346,7 +354,8 @@
             .getPackagesForUid(anyInt());
         mPointingAppController.mUidImportanceListener.onUidImportance(1, IMPORTANCE_GONE);
         ArgumentCaptor<Intent> restartedIntentCaptor = ArgumentCaptor.forClass(Intent.class);
-        mInOrderForPointingUi.verify(mContext).startActivity(restartedIntentCaptor.capture());
+        mInOrderForPointingUi.verify(mContext).startActivityAsUser(restartedIntentCaptor.capture(),
+                eq(UserHandle.CURRENT));
         Intent restartIntent = restartedIntentCaptor.getValue();
         assertEquals(KEY_POINTING_UI_PACKAGE_NAME, restartIntent.getComponent().getPackageName());
         assertEquals(KEY_POINTING_UI_CLASS_NAME, restartIntent.getComponent().getClassName());
@@ -413,7 +422,6 @@
         TestSatelliteTransmissionUpdateCallback callback2 = new
                 TestSatelliteTransmissionUpdateCallback();
         int subId1 = 3;
-        int subId2 = 4;
         mPointingAppController.registerForSatelliteTransmissionUpdates(subId1, callback1);
         mInOrder.verify(mMockSatelliteModemInterface).registerForSatellitePositionInfoChanged(any(),
                 eq(1), eq(null));
@@ -424,16 +432,6 @@
                 .registerForSatellitePositionInfoChanged(any(), eq(1), eq(null));
         mInOrder.verify(mMockSatelliteModemInterface, never())
                 .registerForDatagramTransferStateChanged(any(), eq(4), eq(null));
-        mPointingAppController.registerForSatelliteTransmissionUpdates(subId2, callback1);
-        mInOrder.verify(mMockSatelliteModemInterface).registerForSatellitePositionInfoChanged(any(),
-                eq(1), eq(null));
-        mInOrder.verify(mMockSatelliteModemInterface).registerForDatagramTransferStateChanged(any(),
-                eq(4), eq(null));
-        mPointingAppController.registerForSatelliteTransmissionUpdates(subId2, callback2);
-        mInOrder.verify(mMockSatelliteModemInterface, never())
-                .registerForSatellitePositionInfoChanged(any(), eq(1), eq(null));
-        mInOrder.verify(mMockSatelliteModemInterface, never())
-                .registerForDatagramTransferStateChanged(any(), eq(4), eq(null));
         mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId1,
                 mResultListener::offer, callback1);
         processAllMessages();
@@ -446,22 +444,6 @@
                 any(Handler.class));
         mInOrder.verify(mMockSatelliteModemInterface).unregisterForDatagramTransferStateChanged(
                 any(Handler.class));
-        mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2,
-                mResultListener::offer, callback1);
-        processAllMessages();
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_SUCCESS);
-        mResultListener.remove();
-        mInOrder.verify(mMockSatelliteModemInterface, never())
-                .unregisterForSatellitePositionInfoChanged(any(Handler.class));
-        mInOrder.verify(mMockSatelliteModemInterface, never())
-                .unregisterForDatagramTransferStateChanged(any(Handler.class));
-        mPointingAppController.unregisterForSatelliteTransmissionUpdates(subId2,
-                mResultListener::offer, callback2);
-        processAllMessages();
-        mInOrder.verify(mMockSatelliteModemInterface).unregisterForSatellitePositionInfoChanged(
-                any(Handler.class));
-        mInOrder.verify(mMockSatelliteModemInterface).unregisterForDatagramTransferStateChanged(
-                any(Handler.class));
         mInOrder = null;
     }
 
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 d9ef95a..46252a2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
@@ -16,9 +16,21 @@
 
 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.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;
 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_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.NetworkRegistrationInfo.SERVICE_TYPE_DATA;
 import static android.telephony.SubscriptionManager.SATELLITE_ENTITLEMENT_STATUS;
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_GOOD;
@@ -26,13 +38,18 @@
 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;
+import static android.telephony.satellite.SatelliteManager.KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN;
 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_CAPABILITIES;
 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED;
 import static android.telephony.satellite.SatelliteManager.KEY_SATELLITE_ENABLED;
 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;
@@ -41,18 +58,21 @@
 import static android.telephony.satellite.SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_DISABLE_IN_PROGRESS;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_ERROR;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_INVALID_ARGUMENTS;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_ERROR;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
-import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NOT_AUTHORIZED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_NO_RESOURCES;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_IN_PROGRESS;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SERVICE_NOT_PROVISIONED;
@@ -65,10 +85,12 @@
 
 import static org.junit.Assert.assertEquals;
 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;
@@ -81,6 +103,7 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
@@ -89,9 +112,18 @@
 import static org.mockito.Mockito.when;
 
 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;
@@ -99,14 +131,22 @@
 import android.os.ICancellationSignal;
 import android.os.Looper;
 import android.os.Message;
+import android.os.OutcomeReceiver;
 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.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.satellite.INtnSignalStrengthCallback;
 import android.telephony.satellite.ISatelliteCapabilitiesCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
@@ -119,6 +159,10 @@
 import android.telephony.satellite.SatelliteDatagram;
 import android.telephony.satellite.SatelliteManager;
 import android.telephony.satellite.SatelliteManager.SatelliteException;
+import android.telephony.satellite.SatelliteModemEnableRequestAttributes;
+import android.telephony.satellite.SatelliteSubscriberInfo;
+import android.telephony.satellite.SatelliteSubscriberProvisionStatus;
+import android.telephony.satellite.SatelliteSubscriptionInfo;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Pair;
@@ -137,24 +181,28 @@
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
 import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats;
 import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 
 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;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -177,6 +225,8 @@
     private static final int[] ACTIVE_SUB_IDS = {SUB_ID};
     private static final int TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS =
             (int) TimeUnit.SECONDS.toMillis(60);
+    private 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>>
@@ -187,6 +237,7 @@
     private PersistableBundle mCarrierConfigBundle;
     private ServiceState mServiceState2;
 
+    @Mock private SatelliteController mMockSatelliteController;
     @Mock private DatagramController mMockDatagramController;
     @Mock private SatelliteModemInterface mMockSatelliteModemInterface;
     @Mock private SatelliteSessionController mMockSatelliteSessionController;
@@ -205,6 +256,10 @@
     @Mock private CellSignalStrength mCellSignalStrength;
     @Mock private SatelliteConfig mMockConfig;
     @Mock private DemoSimulator mMockDemoSimulator;
+    @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() {
@@ -233,6 +288,17 @@
     private SatelliteCapabilities mEmptySatelliteCapabilities = new SatelliteCapabilities(
             new HashSet<>(), mIsPointingRequired, MAX_BYTES_PER_OUT_GOING_DATAGRAM,
             new HashMap<>());
+    final int mCarrierId = 0;
+    final String mImsi = "1234567890123";
+    final String mNiddApn = "testApn";
+    final String mMsisdn = "0987654321";
+    final String mSubscriberId = mImsi.substring(0, 6) + mMsisdn;
+    final String mIccId = "1000000000000001";
+    final String mIccId2 = "2000000000000002";
+    final String mImsi2 = "2345678901234";
+    final String mMsisdn2 = "9876543210";
+    final String mSubscriberId2 = mIccId2;
+
     private Semaphore mSatelliteCapabilitiesSemaphore = new Semaphore(0);
     private SatelliteCapabilities mQueriedSatelliteCapabilities = null;
     private int mQueriedSatelliteCapabilitiesResultCode = SATELLITE_RESULT_SUCCESS;
@@ -453,12 +519,35 @@
         }
     };
 
+    private boolean mRequestIsEmergency = false;
+    private ResultReceiver mRequestIsEmergencyReceiver = new ResultReceiver(null) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            logd("requestIsEmergencyReceiver: resultCode=" + resultCode);
+            if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                if (resultData.containsKey(KEY_EMERGENCY_MODE_ENABLED)) {
+                    mRequestIsEmergency = resultData.getBoolean(
+                            KEY_EMERGENCY_MODE_ENABLED);
+                } else {
+                    loge("KEY_EMERGENCY_MODE_ENABLED does not exist.");
+
+                }
+            }
+        }
+    };
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         MockitoAnnotations.initMocks(this);
         logd(TAG + " Setup!");
 
+        replaceInstance(SatelliteController.class, "sInstance", null,
+                mMockSatelliteController);
         replaceInstance(DatagramController.class, "sInstance", null,
                 mMockDatagramController);
         replaceInstance(SatelliteModemInterface.class, "sInstance", null,
@@ -480,7 +569,9 @@
                 null, mMockTelephonyConfigUpdateInstallReceiver);
         replaceInstance(DemoSimulator.class, "sInstance", null, mMockDemoSimulator);
 
-        mServiceState2 = Mockito.mock(ServiceState.class);
+        doNothing().when(mMockSatelliteController).moveSatelliteToOffStateAndCleanUpResources(
+                SATELLITE_RESULT_REQUEST_ABORTED);
+        mServiceState2 = mock(ServiceState.class);
         when(mPhone.getServiceState()).thenReturn(mServiceState);
         when(mPhone.getSubId()).thenReturn(SUB_ID);
         when(mPhone.getPhoneId()).thenReturn(0);
@@ -494,6 +585,12 @@
         mContextFixture.putIntResource(
                 R.integer.config_wait_for_satellite_enabling_response_timeout_millis,
                 TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS);
+        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();
@@ -521,6 +618,7 @@
                 .onSatelliteEnabledStateChanged(anyBoolean());
         doNothing().when(mMockSatelliteSessionController).onSatelliteModemStateChanged(anyInt());
         doNothing().when(mMockSatelliteSessionController).setDemoMode(anyBoolean());
+        doNothing().when(mMockSatelliteSessionController).cleanUpResource();
         doNothing().when(mMockControllerMetricsStats).onSatelliteEnabled();
         doNothing().when(mMockControllerMetricsStats).reportServiceEnablementSuccessCount();
         doNothing().when(mMockControllerMetricsStats).reportServiceEnablementFailCount();
@@ -538,15 +636,20 @@
                 .when(mMockSessionMetricsStats).setSessionDurationSec(anyInt());
         doReturn(mMockSessionMetricsStats)
                 .when(mMockSessionMetricsStats).setIsDemoMode(anyBoolean());
+        doReturn(mMockSessionMetricsStats)
+                .when(mMockSessionMetricsStats).setCarrierId(anyInt());
         doNothing().when(mMockSessionMetricsStats).reportSessionMetrics();
 
         doReturn(mMockProvisionMetricsStats).when(mMockProvisionMetricsStats)
                 .setResultCode(anyInt());
         doReturn(mMockProvisionMetricsStats).when(mMockProvisionMetricsStats)
                 .setIsProvisionRequest(anyBoolean());
+        doReturn(mMockProvisionMetricsStats).when(mMockProvisionMetricsStats)
+                .setCarrierId(anyInt());
         doNothing().when(mMockProvisionMetricsStats).reportProvisionMetrics();
         doNothing().when(mMockControllerMetricsStats).reportDeprovisionCount(anyInt());
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
         doReturn(mSST).when(mPhone).getServiceStateTracker();
         doReturn(mSST).when(mPhone2).getServiceStateTracker();
         doReturn(mServiceState).when(mSST).getServiceState();
@@ -556,10 +659,6 @@
                 Context.NOTIFICATION_SERVICE);
         mSatelliteControllerUT =
                 new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
-        verify(mMockSatelliteModemInterface).registerForSatelliteProvisionStateChanged(
-                any(Handler.class),
-                eq(26) /* EVENT_SATELLITE_PROVISION_STATE_CHANGED */,
-                eq(null));
         verify(mMockSatelliteModemInterface).registerForPendingDatagrams(
                 any(Handler.class),
                 eq(27) /* EVENT_PENDING_DATAGRAMS */,
@@ -571,6 +670,9 @@
 
         doReturn(mMockConfigParser).when(mMockTelephonyConfigUpdateInstallReceiver)
                 .getConfigParser(ConfigProviderAdaptor.DOMAIN_SATELLITE);
+        doReturn(mSubscriptionInfo).when(mMockSubscriptionManagerService).getSubscriptionInfo(
+                anyInt());
+        doReturn("").when(mSubscriptionInfo).getIccId();
     }
 
     @After
@@ -581,18 +683,45 @@
     }
 
     @Test
+    public void testShouldTurnOffCarrierSatelliteForEmergencyCall() throws Exception {
+        DatagramController datagramController = mock(DatagramController.class);
+        replaceInstance(SatelliteController.class, "mDatagramController",
+                mSatelliteControllerUT, datagramController);
+
+        // Verify should turn off satellite
+        mCarrierConfigBundle.putBoolean(
+                KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL, true);
+        doReturn(false).when(datagramController).isEmergencyCommunicationEstablished();
+        invokeCarrierConfigChanged();
+        mSatelliteControllerUT.setSatellitePhone(1);
+        processAllMessages();
+
+        assertTrue(mSatelliteControllerUT.shouldTurnOffCarrierSatelliteForEmergencyCall());
+
+        // Verify should NOT turn off satellite
+        mCarrierConfigBundle.putBoolean(
+                KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL, false);
+        doReturn(true).when(datagramController).isEmergencyCommunicationEstablished();
+        invokeCarrierConfigChanged();
+        mSatelliteControllerUT.setSatellitePhone(1);
+        processAllMessages();
+
+        assertFalse(mSatelliteControllerUT.shouldTurnOffCarrierSatelliteForEmergencyCall());
+    }
+
+    @Test
     public void testRequestTimeForNextSatelliteVisibility() {
         mSatelliteVisibilityTimeSemaphore.drainPermits();
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
+        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(
                 mSatelliteVisibilityTimeReceiver);
         processAllMessages();
         assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
         assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, mQueriedSatelliteVisibilityTimeResultCode);
 
         resetSatelliteControllerUT();
-        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
+        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(
                 mSatelliteVisibilityTimeReceiver);
         processAllMessages();
         assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
@@ -604,11 +733,11 @@
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestTimeForNextSatelliteVisibility(mSatelliteNextVisibilityTime,
                 SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
+        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(
                 mSatelliteVisibilityTimeReceiver);
         processAllMessages();
         assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
-        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+        assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
                 mQueriedSatelliteVisibilityTimeResultCode);
 
         resetSatelliteControllerUT();
@@ -618,7 +747,7 @@
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestTimeForNextSatelliteVisibility(mSatelliteNextVisibilityTime,
                 SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
+        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(
                 mSatelliteVisibilityTimeReceiver);
         processAllMessages();
         assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
@@ -629,7 +758,7 @@
         provisionSatelliteService();
         setUpResponseForRequestTimeForNextSatelliteVisibility(mSatelliteNextVisibilityTime,
                 SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
+        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(
                 mSatelliteVisibilityTimeReceiver);
         processAllMessages();
         assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
@@ -640,7 +769,7 @@
         provisionSatelliteService();
         setUpNullResponseForRequestTimeForNextSatelliteVisibility(
                 SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
+        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(
                 mSatelliteVisibilityTimeReceiver);
         processAllMessages();
         assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
@@ -651,7 +780,7 @@
         provisionSatelliteService();
         setUpNullResponseForRequestTimeForNextSatelliteVisibility(
                 SATELLITE_RESULT_INVALID_MODEM_STATE);
-        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(SUB_ID,
+        mSatelliteControllerUT.requestTimeForNextSatelliteVisibility(
                 mSatelliteVisibilityTimeReceiver);
         processAllMessages();
         assertTrue(waitForRequestTimeForNextSatelliteVisibilityResult(1));
@@ -706,6 +835,87 @@
         processAllMessages();
         verify(mMockSatelliteModemInterface, times(5))
                 .requestIsSatelliteSupported(any(Message.class));
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Radio is off during TN -> NTN image switch, SatelliteController should not set radio
+        // state to OFF
+        setRadioPower(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Turn on radio
+        setRadioPower(true);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // APM is triggered
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertTrue(mSatelliteControllerUT.isRadioOffRequested());
+        assertTrue(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // SatelliteController should set the radio state to OFF
+        setRadioPower(false);
+        processAllMessages();
+        assertFalse(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Turn on radio
+        setRadioPower(true);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // APM is triggered
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertTrue(mSatelliteControllerUT.isRadioOffRequested());
+        assertTrue(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Modem fails to power off radio. APM is disabled
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(true);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // APM is triggered
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertTrue(mSatelliteControllerUT.isRadioOffRequested());
+        assertTrue(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // The timer WaitForCellularModemOff time out
+        moveTimeForward(TEST_WAIT_FOR_CELLULAR_MODEM_OFF_TIMEOUT_MILLIS);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // APM is triggered
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertTrue(mSatelliteControllerUT.isRadioOffRequested());
+        assertTrue(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
+
+        // Modem failed to power off the radio
+        mSatelliteControllerUT.onPowerOffCellularRadioFailed();
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isRadioOn());
+        assertFalse(mSatelliteControllerUT.isRadioOffRequested());
+        assertFalse(mSatelliteControllerUT.isWaitForCellularModemOffTimerStarted());
     }
 
     @Test
@@ -741,12 +951,13 @@
 
     @Test
     public void testRequestSatelliteEnabled() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
         mIsSatelliteEnabledSemaphore.drainPermits();
 
         // Fail to enable satellite when SatelliteController is not fully loaded yet.
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mIIntegerConsumerSemaphore.drainPermits();
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
@@ -754,47 +965,61 @@
 
         // Fail to enable satellite when the device does not support satellite.
         mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
 
         // Fail to enable satellite when the device is not provisioned yet.
         mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
         resetSatelliteControllerUT();
         verify(mMockSatelliteSessionController, times(1)).onSatelliteEnabledStateChanged(eq(false));
-        verify(mMockSatelliteSessionController, times(1)).setDemoMode(eq(false));
-        verify(mMockDatagramController, times(1)).setDemoMode(eq(false));
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
                 (long) mIIntegerConsumerResults.get(0));
 
-        sendProvisionedStateChangedEvent(true, null);
+        setProvisionedState(true);
         processAllMessages();
         verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
 
-        // Successfully enable satellite
+        // Fail to enable satellite when the emergency call is in progress
         mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
         mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
         setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        doReturn(true).when(mTelecomManager).isInEmergencyCall();
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
+        mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS,
+                (long) mIIntegerConsumerResults.get(0));
+        doReturn(false).when(mTelecomManager).isInEmergencyCall();
+
+        // Successfully enable satellite
+        mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
+        mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
+        mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
+        setUpResponseForRequestSatelliteEnabled(true, false, true, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, true, mIIntegerConsumer);
         mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
         verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+        verify(mMockSatelliteSessionController, times(1)).onEmergencyModeChanged(eq(true));
         assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
         assertTrue(mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled);
         assertEquals(
@@ -806,29 +1031,35 @@
         verify(mMockControllerMetricsStats, times(1)).reportServiceEnablementSuccessCount();
 
         // Successfully disable satellite when radio is turned off.
+        clearInvocations(mMockSatelliteSessionController);
+        clearInvocations(mMockDatagramController);
+        mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
+        mSatelliteControllerUT.isSatelliteBeingDisabled = true;
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
         mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
         setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
         setRadioPower(false);
-        mSatelliteControllerUT.onCellularRadioPowerOffRequested();
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
         processAllMessages();
         sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_OFF, null);
         processAllMessages();
         verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+        verify(mMockSatelliteSessionController, times(1)).onEmergencyModeChanged(eq(false));
         assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
         assertTrue(mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled);
         assertEquals(
                 SATELLITE_MODE_ENABLED_FALSE, mSatelliteControllerUT.satelliteModeSettingValue);
         verify(mMockSatelliteSessionController, times(2)).onSatelliteEnabledStateChanged(eq(false));
-        verify(mMockSatelliteSessionController, times(3)).setDemoMode(eq(false));
-        verify(mMockDatagramController, times(3)).setDemoMode(eq(false));
+        verify(mMockSatelliteSessionController, times(2)).setDemoMode(eq(false));
+        verify(mMockDatagramController, times(2)).setDemoMode(eq(false));
         verify(mMockControllerMetricsStats, times(1)).onSatelliteDisabled();
+        mSatelliteControllerUT.isSatelliteBeingDisabled = false;
 
         // Fail to enable satellite when radio is off.
         mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
         setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         // Radio is not on, can not enable satellite
@@ -840,13 +1071,13 @@
 
         // Fail to enable satellite with an error response from modem when radio is on.
         mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
         clearInvocations(mMockPointingAppController);
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
         mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
         setUpResponseForRequestSatelliteEnabled(true, false, false,
                 SATELLITE_RESULT_INVALID_MODEM_STATE);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
@@ -859,11 +1090,11 @@
 
         // Successfully enable satellite when radio is on.
         mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
         mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
         setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -871,16 +1102,16 @@
         assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
         assertTrue(mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled);
         assertEquals(SATELLITE_MODE_ENABLED_TRUE, mSatelliteControllerUT.satelliteModeSettingValue);
-        verify(mMockSatelliteSessionController, times(2)).onSatelliteEnabledStateChanged(eq(true));
-        verify(mMockSatelliteSessionController, times(4)).setDemoMode(eq(false));
-        verify(mMockDatagramController, times(4)).setDemoMode(eq(false));
+        verify(mMockSatelliteSessionController, times(1)).onSatelliteEnabledStateChanged(eq(true));
+        verify(mMockSatelliteSessionController, times(3)).setDemoMode(eq(false));
+        verify(mMockDatagramController, times(3)).setDemoMode(eq(false));
         verify(mMockControllerMetricsStats, times(2)).onSatelliteEnabled();
         verify(mMockControllerMetricsStats, times(2)).reportServiceEnablementSuccessCount();
 
         // Successfully enable satellite when it is already enabled.
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mIIntegerConsumerSemaphore.drainPermits();
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -888,8 +1119,8 @@
 
         // Fail to enable satellite with a different demo mode when it is already enabled.
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, true, false,
-                mIIntegerConsumer);
+        mIIntegerConsumerSemaphore.drainPermits();
+        mSatelliteControllerUT.requestSatelliteEnabled(true, true, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_INVALID_ARGUMENTS, (long) mIIntegerConsumerResults.get(0));
@@ -897,9 +1128,9 @@
 
         // Successfully disable satellite.
         mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
         setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(false, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -907,8 +1138,8 @@
 
         // Disable satellite when satellite is already disabled.
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
-                mIIntegerConsumer);
+        mIIntegerConsumerSemaphore.drainPermits();
+        mSatelliteControllerUT.requestSatelliteEnabled(false, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -916,8 +1147,8 @@
 
         // Disable satellite with a different demo mode when satellite is already disabled.
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, true, false,
-                mIIntegerConsumer);
+        mIIntegerConsumerSemaphore.drainPermits();
+        mSatelliteControllerUT.requestSatelliteEnabled(false, true, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -925,110 +1156,229 @@
 
         // Send a second request while the first request in progress
         mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
         setUpNoResponseForRequestSatelliteEnabled(true, false, false);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_REQUEST_IN_PROGRESS, (long) mIIntegerConsumerResults.get(0));
 
         mIIntegerConsumerResults.clear();
-        resetSatelliteControllerUTToSupportedAndProvisionedState();
-        // Should receive callback for the above request when satellite modem is turned off.
+        mIIntegerConsumerSemaphore.drainPermits();
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setProvisionedState(false);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setProvisionedState(true);
+        processAllMessages();
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        // The enable request should be aborted when satellite modem move to OFF state.
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_REQUEST_ABORTED, (long) mIIntegerConsumerResults.get(0));
+
+        // Successfully enable satellite
+        mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
 
         // Move to satellite-disabling in progress.
+        mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
         setUpNoResponseForRequestSatelliteEnabled(false, false, false);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(false, false, false, mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
 
         // Disable is in progress. Thus, a new request to enable satellite will be rejected.
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mIIntegerConsumerSemaphore.drainPermits();
+        mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
+        mSatelliteControllerUT.isSatelliteBeingDisabled = true;
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_ERROR, (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_DISABLE_IN_PROGRESS, (long) mIIntegerConsumerResults.get(0));
 
         mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
         resetSatelliteControllerUTToOffAndProvisionedState();
-        // Should receive callback for the above request when satellite modem is turned off.
-        assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
-
-        verify(mMockSessionMetricsStats, times(15)).setInitializationResult(anyInt());
-        verify(mMockSessionMetricsStats, times(15)).setSatelliteTechnology(anyInt());
-        verify(mMockSessionMetricsStats, times(3)).setInitializationProcessingTime(anyLong());
-        verify(mMockSessionMetricsStats, times(2)).setTerminationResult(anyInt());
-        verify(mMockSessionMetricsStats, times(2)).setTerminationProcessingTime(anyLong());
-        verify(mMockSessionMetricsStats, times(2)).setSessionDurationSec(anyInt());
-        verify(mMockSessionMetricsStats, times(15)).reportSessionMetrics();
+        mSatelliteControllerUT.isSatelliteBeingDisabled = false;
 
         /**
          * Make areAllRadiosDisabled return false and move mWaitingForRadioDisabled to true, which
          * will lead to no response for requestSatelliteEnabled.
          */
         mSatelliteControllerUT.allRadiosDisabled = false;
+        mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
         setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
 
-        resetSatelliteControllerUTEnabledState();
         mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
         setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(false, false, false, mIIntegerConsumer);
         processAllMessages();
         // We should receive 2 callbacks for the above 2 requests.
         assertTrue(waitForIIntegerConsumerResult(2));
+        // Successful result for disable request
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
-        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(1));
+        // The enable request should be aborted after getting the successful confirmation of the
+        // disable request.
+        assertEquals(SATELLITE_RESULT_REQUEST_ABORTED, (long) mIIntegerConsumerResults.get(1));
 
         resetSatelliteControllerUTToOffAndProvisionedState();
 
         // Repeat the same test as above but with error response from modem for the second request
         mSatelliteControllerUT.allRadiosDisabled = false;
         setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
+        // No response for the enable request because all radios are not disabled yet
         assertFalse(waitForIIntegerConsumerResult(1));
 
-        resetSatelliteControllerUTEnabledState();
         mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
         setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_NO_RESOURCES);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(false, false, false, mIIntegerConsumer);
         processAllMessages();
-        // We should receive 2 callbacks for the above 2 requests.
-        assertTrue(waitForIIntegerConsumerResult(2));
-        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
-        assertEquals(SATELLITE_RESULT_NO_RESOURCES, (long) mIIntegerConsumerResults.get(1));
+        // We should receive result for the disable request.
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_NO_RESOURCES, (long) mIIntegerConsumerResults.get(0));
         mSatelliteControllerUT.allRadiosDisabled = true;
 
         resetSatelliteControllerUTToOnAndProvisionedState();
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
-        mSatelliteControllerUT.onCellularRadioPowerOffRequested();
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
         processAllMessages();
         // Satellite should not be powered off since the feature flag oemEnabledSatelliteFlag is
         // disabled
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
         verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+
+        // Successfully disable satellite.
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
+        setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(false, false, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+
+        // Fail to enable satellite when radio is being powered off.
+        mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.onSetCellularRadioPowerStateRequested(false);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        // Radio is being powered off, can not enable satellite
+        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
+
+        // Modem failed to power off
+        mSatelliteControllerUT.onPowerOffCellularRadioFailed();
+
+        // Successfully enable satellite when radio is on.
+        mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
+        mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
+        mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+
+        // Clean up all previous resources
+        processAllFutureMessages();
+        mIIntegerConsumerSemaphore.drainPermits();
+
+        // Successfully disable satellite.
+        mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
+        setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(false, false, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+
+        // Move to satellite-enabling in progress.
+        setUpNoResponseForRequestSatelliteEnabled(true, false, false);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
+        processAllMessages();
+        assertFalse(waitForIIntegerConsumerResult(1));
+
+        // Successfully disable satellite.
+        mIIntegerConsumerResults.clear();
+        mIIntegerConsumerSemaphore.drainPermits();
+        setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(false, false, false, mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(2));
+        // Should get success result for the disable request
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        // The enable request should be aborted
+        assertEquals(SATELLITE_RESULT_REQUEST_ABORTED, (long) mIIntegerConsumerResults.get(1));
+        // All timers waiting for enablement response should be stopped
+        assertFalse(mSatelliteControllerUT.isAnyWaitForSatelliteEnablingResponseTimerStarted());
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+    }
+
+    @Test
+    public void testGetRequestIsEmergency() {
+        mIsSatelliteEnabledSemaphore.drainPermits();
+        doReturn(true).when(mFeatureFlags).carrierRoamingNbIotNtn();
+
+        // Successfully enable satellite
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+
+        // Set provisioned state
+        setProvisionedState(true);
+        processAllMessages();
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+
+        mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
+        mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
+        // Set response for enabling request
+        setUpResponseForRequestSatelliteEnabled(true, false, true/*emergency*/,
+                SATELLITE_RESULT_SUCCESS);
+        // Request satellite enabling for emergency
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, true /*isEmergency*/,
+                mIIntegerConsumer);
+        mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
+        processAllMessages();
+
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+
+        // Verify satellite enabled for emergency
+        assertTrue(mSatelliteControllerUT.getRequestIsEmergency());
+        mSatelliteControllerUT.requestIsEmergencyModeEnabled(mRequestIsEmergencyReceiver);
+        assertTrue(mRequestIsEmergency);
     }
 
     @Test
     public void testRequestSatelliteCapabilities() {
         mSatelliteCapabilitiesSemaphore.drainPermits();
-        mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        mSatelliteControllerUT.requestSatelliteCapabilities(mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
         assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
@@ -1036,7 +1386,7 @@
 
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        mSatelliteControllerUT.requestSatelliteCapabilities(mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
         assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, mQueriedSatelliteCapabilitiesResultCode);
@@ -1046,7 +1396,7 @@
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestSatelliteCapabilities(mSatelliteCapabilities,
                 SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        mSatelliteControllerUT.requestSatelliteCapabilities(mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteCapabilitiesResultCode);
@@ -1059,7 +1409,7 @@
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpNullResponseForRequestSatelliteCapabilities(SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        mSatelliteControllerUT.requestSatelliteCapabilities(mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
         assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
@@ -1069,7 +1419,7 @@
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpNullResponseForRequestSatelliteCapabilities(SATELLITE_RESULT_INVALID_MODEM_STATE);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        mSatelliteControllerUT.requestSatelliteCapabilities(mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
         assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, mQueriedSatelliteCapabilitiesResultCode);
@@ -1079,7 +1429,7 @@
     public void testStartSatelliteTransmissionUpdates() {
         mIIntegerConsumerSemaphore.drainPermits();
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+        mSatelliteControllerUT.startSatelliteTransmissionUpdates(mIIntegerConsumer,
                 mStartTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -1089,7 +1439,7 @@
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+        mSatelliteControllerUT.startSatelliteTransmissionUpdates(mIIntegerConsumer,
                 mStartTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -1099,11 +1449,11 @@
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+        mSatelliteControllerUT.startSatelliteTransmissionUpdates(mIIntegerConsumer,
                 mStartTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+        assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
                 (long) mIIntegerConsumerResults.get(0));
 
         resetSatelliteControllerUT();
@@ -1113,7 +1463,7 @@
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+        mSatelliteControllerUT.startSatelliteTransmissionUpdates(mIIntegerConsumer,
                 mStartTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -1124,7 +1474,7 @@
         provisionSatelliteService();
         mIIntegerConsumerResults.clear();
         setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+        mSatelliteControllerUT.startSatelliteTransmissionUpdates(mIIntegerConsumer,
                 mStartTransmissionUpdateCallback);
         verify(mMockPointingAppController).registerForSatelliteTransmissionUpdates(anyInt(),
                 eq(mStartTransmissionUpdateCallback));
@@ -1138,7 +1488,7 @@
         provisionSatelliteService();
         mIIntegerConsumerResults.clear();
         setUpResponseForStartSatelliteTransmissionUpdates(SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
-        mSatelliteControllerUT.startSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+        mSatelliteControllerUT.startSatelliteTransmissionUpdates(mIIntegerConsumer,
                 mStartTransmissionUpdateCallback);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -1151,54 +1501,11 @@
 
     @Test
     public void testStopSatelliteTransmissionUpdates() {
-        mIIntegerConsumerSemaphore.drainPermits();
-        mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
-                mStopTransmissionUpdateCallback);
-        processAllMessages();
-        assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
-                (long) mIIntegerConsumerResults.get(0));
-
-        mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
-                mStopTransmissionUpdateCallback);
-        processAllMessages();
-        assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
-
-        resetSatelliteControllerUT();
-        mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
-                mStopTransmissionUpdateCallback);
-        processAllMessages();
-        assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
-                (long) mIIntegerConsumerResults.get(0));
-
-        resetSatelliteControllerUT();
-        mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
-                mStopTransmissionUpdateCallback);
-        processAllMessages();
-        assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
-                (long) mIIntegerConsumerResults.get(0));
-
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
         provisionSatelliteService();
         setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+        mSatelliteControllerUT.stopSatelliteTransmissionUpdates(mIIntegerConsumer,
                 mStopTransmissionUpdateCallback);
         verify(mMockPointingAppController).unregisterForSatelliteTransmissionUpdates(anyInt(),
                 any(), eq(mStopTransmissionUpdateCallback));
@@ -1211,9 +1518,13 @@
         provisionSatelliteService();
         mIIntegerConsumerResults.clear();
         setUpResponseForStopSatelliteTransmissionUpdates(SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
-        mSatelliteControllerUT.stopSatelliteTransmissionUpdates(SUB_ID, mIIntegerConsumer,
+        mSatelliteControllerUT.stopSatelliteTransmissionUpdates(mIIntegerConsumer,
                 mStopTransmissionUpdateCallback);
+        verify(mMockPointingAppController, times(2)).unregisterForSatelliteTransmissionUpdates(
+                anyInt(), any(), eq(mStopTransmissionUpdateCallback));
         processAllMessages();
+        verify(mMockPointingAppController, times(2)).stopSatelliteTransmissionUpdates(
+                any(Message.class));
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
                 (long) mIIntegerConsumerResults.get(0));
@@ -1223,7 +1534,7 @@
     public void testRequestIsDemoModeEnabled() {
         mIsDemoModeEnabledSemaphore.drainPermits();
         resetSatelliteControllerUT();
-        mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
+        mSatelliteControllerUT.requestIsDemoModeEnabled(mIsDemoModeEnabledReceiver);
         assertTrue(waitForRequestIsDemoModeEnabledResult(1));
         assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, mQueriedIsDemoModeEnabledResultCode);
         assertFalse(mQueriedIsDemoModeEnabled);
@@ -1231,7 +1542,7 @@
         resetSatelliteControllerUT();
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
+        mSatelliteControllerUT.requestIsDemoModeEnabled(mIsDemoModeEnabledReceiver);
         assertTrue(waitForRequestIsDemoModeEnabledResult(1));
         assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, mQueriedIsDemoModeEnabledResultCode);
         assertFalse(mQueriedIsDemoModeEnabled);
@@ -1239,9 +1550,9 @@
         resetSatelliteControllerUT();
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
+        mSatelliteControllerUT.requestIsDemoModeEnabled(mIsDemoModeEnabledReceiver);
         assertTrue(waitForRequestIsDemoModeEnabledResult(1));
-        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, mQueriedIsDemoModeEnabledResultCode);
+        assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED, mQueriedIsDemoModeEnabledResultCode);
         assertFalse(mQueriedIsDemoModeEnabled);
 
         resetSatelliteControllerUT();
@@ -1249,7 +1560,7 @@
         setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
+        mSatelliteControllerUT.requestIsDemoModeEnabled(mIsDemoModeEnabledReceiver);
         assertTrue(waitForRequestIsDemoModeEnabledResult(1));
         assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED, mQueriedIsDemoModeEnabledResultCode);
         assertFalse(mQueriedIsDemoModeEnabled);
@@ -1257,7 +1568,7 @@
         resetSatelliteControllerUT();
         boolean isDemoModeEnabled = mSatelliteControllerUT.isDemoModeEnabled();
         provisionSatelliteService();
-        mSatelliteControllerUT.requestIsDemoModeEnabled(SUB_ID, mIsDemoModeEnabledReceiver);
+        mSatelliteControllerUT.requestIsDemoModeEnabled(mIsDemoModeEnabledReceiver);
         assertTrue(waitForRequestIsDemoModeEnabledResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedIsDemoModeEnabledResultCode);
         assertEquals(isDemoModeEnabled, mQueriedIsDemoModeEnabled);
@@ -1265,29 +1576,32 @@
 
     @Test
     public void testIsSatelliteEnabled() {
+        logd("testIsSatelliteEnabled: starting");
         setUpResponseForRequestIsSatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
-        assertFalse(mSatelliteControllerUT.isSatelliteEnabled());
+        assertFalse(mSatelliteControllerUT.isSatelliteEnabledOrBeingEnabled());
         mIsSatelliteEnabledSemaphore.drainPermits();
-        mSatelliteControllerUT.requestIsSatelliteEnabled(SUB_ID, mIsSatelliteEnabledReceiver);
+        mSatelliteControllerUT.requestIsSatelliteEnabled(mIsSatelliteEnabledReceiver);
         processAllMessages();
         assertTrue(waitForRequestIsSatelliteEnabledResult(1));
         assertEquals(
                 SATELLITE_RESULT_INVALID_TELEPHONY_STATE, mQueriedIsSatelliteEnabledResultCode);
 
 
+        logd("testIsSatelliteEnabled: setUpResponseForRequestIsSatelliteSupported");
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        logd("testIsSatelliteEnabled: verifySatelliteSupported");
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestIsSatelliteEnabled(SUB_ID, mIsSatelliteEnabledReceiver);
+        mSatelliteControllerUT.requestIsSatelliteEnabled(mIsSatelliteEnabledReceiver);
         processAllMessages();
         assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedIsSatelliteEnabledResultCode);
-        assertEquals(mSatelliteControllerUT.isSatelliteEnabled(), mQueriedIsSatelliteEnabled);
+        assertEquals(mSatelliteControllerUT.isSatelliteEnabledOrBeingEnabled(),
+                mQueriedIsSatelliteEnabled);
     }
 
     @Test
     public void testOnSatelliteServiceConnected() {
         verifySatelliteSupported(false, SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         verifySatelliteEnabled(false, SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
-        verifySatelliteProvisioned(false, SATELLITE_RESULT_INVALID_TELEPHONY_STATE);
 
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
@@ -1299,7 +1613,7 @@
 
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
     }
 
     @Test
@@ -1309,9 +1623,23 @@
             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);
+            }
         };
-        int errorCode = mSatelliteControllerUT.registerForSatelliteModemStateChanged(
-                SUB_ID, callback);
+        int errorCode = mSatelliteControllerUT.registerForSatelliteModemStateChanged(callback);
         assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, errorCode);
         verify(mMockSatelliteSessionController, never())
                 .registerForSatelliteModemStateChanged(callback);
@@ -1319,8 +1647,7 @@
         resetSatelliteControllerUTToSupportedAndProvisionedState();
         mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
 
-        errorCode = mSatelliteControllerUT.registerForSatelliteModemStateChanged(
-                SUB_ID, callback);
+        errorCode = mSatelliteControllerUT.registerForSatelliteModemStateChanged(callback);
         assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
         verify(mMockSatelliteSessionController).registerForSatelliteModemStateChanged(callback);
     }
@@ -1332,14 +1659,29 @@
             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);
+            }
         };
-        mSatelliteControllerUT.unregisterForModemStateChanged(SUB_ID, callback);
+        mSatelliteControllerUT.unregisterForModemStateChanged(callback);
         verify(mMockSatelliteSessionController, never())
                 .unregisterForSatelliteModemStateChanged(callback);
 
         resetSatelliteControllerUTToSupportedAndProvisionedState();
         mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
-        mSatelliteControllerUT.unregisterForModemStateChanged(SUB_ID, callback);
+        mSatelliteControllerUT.unregisterForModemStateChanged(callback);
         verify(mMockSatelliteSessionController).unregisterForSatelliteModemStateChanged(callback);
     }
 
@@ -1358,31 +1700,54 @@
                                     + "semaphore, ex=" + ex);
                         }
                     }
+
+                    @Override
+                    public void onSatelliteSubscriptionProvisionStateChanged(
+                            List<SatelliteSubscriberProvisionStatus> status) {
+                        logd("onSatelliteSubscriptionProvisionStateChanged: " + status);
+                    }
                 };
-        int errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(
-                SUB_ID, callback);
+        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(
-                SUB_ID, callback);
+        errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(callback);
         assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, errorCode);
 
         resetSatelliteControllerUT();
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(
-                SUB_ID, callback);
+        errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(callback);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteProvisionStateChanged"));
         assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
 
-        sendProvisionedStateChangedEvent(true, null);
+        try {
+            setSatelliteSubscriberTesting();
+        } 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();
+        ICancellationSignal cancelRemote = null;
+        mIIntegerConsumerResults.clear();
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(
+                TEST_SATELLITE_TOKEN,
+                testProvisionData, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForForEvents(
                 semaphore, 1, "testRegisterForSatelliteProvisionStateChanged"));
 
-        mSatelliteControllerUT.unregisterForSatelliteProvisionStateChanged(SUB_ID, callback);
-        sendProvisionedStateChangedEvent(true, null);
+        mSatelliteControllerUT.unregisterForSatelliteProvisionStateChanged(callback);
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(
+                TEST_SATELLITE_TOKEN,
+                testProvisionData, mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForForEvents(
                 semaphore, 1, "testRegisterForSatelliteProvisionStateChanged"));
@@ -1399,11 +1764,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(SUB_ID, callback);
+        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
@@ -1418,9 +1783,9 @@
                     }
                 };
         doNothing().when(mMockDatagramController)
-                .unregisterForSatelliteDatagram(eq(SUB_ID), eq(callback));
-        mSatelliteControllerUT.unregisterForIncomingDatagram(SUB_ID, callback);
-        verify(mMockDatagramController).unregisterForSatelliteDatagram(eq(SUB_ID), eq(callback));
+                .unregisterForSatelliteDatagram(anyInt(), eq(callback));
+        mSatelliteControllerUT.unregisterForIncomingDatagram(callback);
+        verify(mMockDatagramController).unregisterForSatelliteDatagram(anyInt(), eq(callback));
     }
 
     @Test
@@ -1439,7 +1804,7 @@
             clearInvocations(mMockDatagramController);
             clearInvocations(mMockPointingAppController);
 
-            mSatelliteControllerUT.sendDatagram(SUB_ID, datagramType, datagram, true,
+            mSatelliteControllerUT.sendDatagram(datagramType, datagram, true,
                     mIIntegerConsumer);
             processAllMessages();
             assertTrue(waitForIIntegerConsumerResult(1));
@@ -1451,10 +1816,10 @@
             mIIntegerConsumerResults.clear();
             setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
             verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-            sendProvisionedStateChangedEvent(false, null);
+            setProvisionedState(false);
             processAllMessages();
             verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-            mSatelliteControllerUT.sendDatagram(SUB_ID, datagramType, datagram, true,
+            mSatelliteControllerUT.sendDatagram(datagramType, datagram, true,
                     mIIntegerConsumer);
             processAllMessages();
             assertTrue(waitForIIntegerConsumerResult(1));
@@ -1464,10 +1829,10 @@
                     eq(datagramType), eq(datagram), eq(true), any());
 
             mIIntegerConsumerResults.clear();
-            sendProvisionedStateChangedEvent(true, null);
+            setProvisionedState(true);
             processAllMessages();
             verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-            mSatelliteControllerUT.sendDatagram(SUB_ID, datagramType, datagram, true,
+            mSatelliteControllerUT.sendDatagram(datagramType, datagram, true,
                     mIIntegerConsumer);
             processAllMessages();
             assertFalse(waitForIIntegerConsumerResult(1));
@@ -1481,7 +1846,7 @@
     @Test
     public void testPollPendingSatelliteDatagrams() {
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.pollPendingDatagrams(SUB_ID, mIIntegerConsumer);
+        mSatelliteControllerUT.pollPendingDatagrams(mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
@@ -1491,10 +1856,10 @@
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        sendProvisionedStateChangedEvent(false, null);
+        setProvisionedState(false);
         processAllMessages();
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.pollPendingDatagrams(SUB_ID, mIIntegerConsumer);
+        mSatelliteControllerUT.pollPendingDatagrams(mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
@@ -1502,10 +1867,10 @@
         verify(mMockDatagramController, never()).pollPendingSatelliteDatagrams(anyInt(), any());
 
         mIIntegerConsumerResults.clear();
-        sendProvisionedStateChangedEvent(true, null);
+        setProvisionedState(true);
         processAllMessages();
         verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.pollPendingDatagrams(SUB_ID, mIIntegerConsumer);
+        mSatelliteControllerUT.pollPendingDatagrams(mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
         verify(mMockDatagramController, times(1)).pollPendingSatelliteDatagrams(anyInt(), any());
@@ -1513,12 +1878,14 @@
 
     @Test
     public void testProvisionSatelliteService() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(false);
+
         String mText = "This is test provision data.";
         byte[] testProvisionData = mText.getBytes();
         CancellationSignal cancellationSignal = new CancellationSignal();
         ICancellationSignal cancelRemote = null;
         mIIntegerConsumerResults.clear();
-        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(
                 TEST_SATELLITE_TOKEN,
                 testProvisionData, mIIntegerConsumer);
         processAllMessages();
@@ -1531,7 +1898,7 @@
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(
                 TEST_SATELLITE_TOKEN,
                 testProvisionData, mIIntegerConsumer);
         processAllMessages();
@@ -1545,9 +1912,7 @@
         setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN, testProvisionData,
-                SATELLITE_RESULT_SUCCESS);
-        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(
                 TEST_SATELLITE_TOKEN,
                 testProvisionData, mIIntegerConsumer);
         processAllMessages();
@@ -1557,7 +1922,7 @@
         verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
 
         // Send provision request again after the device is successfully provisioned
-        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(
                 TEST_SATELLITE_TOKEN,
                 testProvisionData, mIIntegerConsumer);
         processAllMessages();
@@ -1565,70 +1930,19 @@
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
         assertNull(cancelRemote);
 
-        // Vendor service does not support the request requestIsSatelliteProvisioned. Telephony will
-        // make decision itself
-        resetSatelliteControllerUT();
-        mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(
-                false, SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-
-        // Vendor service does not support the requests requestIsSatelliteProvisioned and
-        // provisionSatelliteService. Telephony will make decision itself
-        deprovisionSatelliteService();
-        resetSatelliteControllerUT();
-        mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(
-                false, SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN, testProvisionData,
-                SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
-        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
-                TEST_SATELLITE_TOKEN,
-                testProvisionData, mIIntegerConsumer);
-        processAllMessages();
-        assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
-        assertNotNull(cancelRemote);
-        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-
-        resetSatelliteControllerUT();
-        mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN, testProvisionData,
-                SATELLITE_RESULT_NOT_AUTHORIZED);
-        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
-                TEST_SATELLITE_TOKEN,
-                testProvisionData, mIIntegerConsumer);
-        processAllMessages();
-        assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_NOT_AUTHORIZED, (long) mIIntegerConsumerResults.get(0));
-        assertNotNull(cancelRemote);
-
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForProvisionSatelliteService(TEST_NEXT_SATELLITE_TOKEN, testProvisionData,
-                SATELLITE_RESULT_SUCCESS);
-        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(
                 TEST_NEXT_SATELLITE_TOKEN, testProvisionData, mIIntegerConsumer);
         cancellationSignal.setRemote(cancelRemote);
         cancellationSignal.cancel();
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
-        verify(mMockSatelliteModemInterface).deprovisionSatelliteService(
-                eq(TEST_NEXT_SATELLITE_TOKEN), any(Message.class));
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
@@ -1636,13 +1950,10 @@
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        setUpNoResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN);
-        setUpResponseForProvisionSatelliteService(TEST_NEXT_SATELLITE_TOKEN, testProvisionData,
-                SATELLITE_RESULT_SUCCESS);
-        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(
                 TEST_SATELLITE_TOKEN,
                 testProvisionData, mIIntegerConsumer);
-        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(
                 TEST_NEXT_SATELLITE_TOKEN,
                 testProvisionData, mIIntegerConsumer);
         processAllMessages();
@@ -1653,11 +1964,12 @@
 
     @Test
     public void testDeprovisionSatelliteService() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(false);
         mIIntegerConsumerSemaphore.drainPermits();
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+        mSatelliteControllerUT.deprovisionSatelliteService(
                 TEST_SATELLITE_TOKEN, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -1665,7 +1977,7 @@
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+        mSatelliteControllerUT.deprovisionSatelliteService(
                 TEST_SATELLITE_TOKEN, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -1676,13 +1988,11 @@
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+        mSatelliteControllerUT.deprovisionSatelliteService(
                 TEST_SATELLITE_TOKEN, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
-                (long) mIIntegerConsumerResults.get(0));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
 
         resetSatelliteControllerUT();
         mIIntegerConsumerResults.clear();
@@ -1690,8 +2000,7 @@
         setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+        mSatelliteControllerUT.deprovisionSatelliteService(
                 TEST_SATELLITE_TOKEN, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -1700,21 +2009,7 @@
         resetSatelliteControllerUT();
         provisionSatelliteService();
         mIIntegerConsumerResults.clear();
-        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
-                TEST_SATELLITE_TOKEN, mIIntegerConsumer);
-        processAllMessages();
-        assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
-        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-
-        // Vendor service does not support deprovisionSatelliteService
-        resetSatelliteControllerUT();
-        provisionSatelliteService();
-        mIIntegerConsumerResults.clear();
-        setUpResponseForDeprovisionSatelliteService(
-                TEST_SATELLITE_TOKEN, SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
-        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+        mSatelliteControllerUT.deprovisionSatelliteService(
                 TEST_SATELLITE_TOKEN, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -1722,16 +2017,16 @@
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
 
         resetSatelliteControllerUT();
-        provisionSatelliteService();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setProvisionedState(null);
         mIIntegerConsumerResults.clear();
-        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN,
-                SATELLITE_RESULT_INVALID_MODEM_STATE);
-        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+        mSatelliteControllerUT.deprovisionSatelliteService(
                 TEST_SATELLITE_TOKEN, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
-        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
     }
 
     @Test
@@ -1741,7 +2036,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"};
@@ -1768,7 +2063,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.
@@ -1784,16 +2079,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
@@ -1812,27 +2107,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)
@@ -1853,33 +2148,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()
@@ -1887,20 +2182,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)
@@ -2209,7 +2504,7 @@
         TestSatelliteController satelliteController =
                 new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
         mSatelliteCapabilitiesSemaphore.drainPermits();
-        satelliteController.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        satelliteController.requestSatelliteCapabilities(mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
         assertEquals(
@@ -2223,7 +2518,7 @@
                 new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
         verifySatelliteSupported(satelliteController, true, SATELLITE_RESULT_SUCCESS);
         mSatelliteCapabilitiesSemaphore.drainPermits();
-        satelliteController.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        satelliteController.requestSatelliteCapabilities(mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteCapabilitiesResultCode);
@@ -2240,7 +2535,7 @@
                 new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
         verifySatelliteSupported(satelliteController, true, SATELLITE_RESULT_SUCCESS);
         mSatelliteCapabilitiesSemaphore.drainPermits();
-        satelliteController.requestSatelliteCapabilities(SUB_ID, mSatelliteCapabilitiesReceiver);
+        satelliteController.requestSatelliteCapabilities(mSatelliteCapabilitiesReceiver);
         processAllMessages();
         assertTrue(waitForRequestSatelliteCapabilitiesResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteCapabilitiesResultCode);
@@ -2269,22 +2564,28 @@
         clearInvocations(mMockDatagramController);
         sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_UNAVAILABLE, null);
         processAllMessages();
-        verify(mMockSatelliteSessionController, times(1)).onSatelliteEnabledStateChanged(eq(false));
-        verify(mMockSatelliteSessionController, times(1)).setDemoMode(eq(false));
-        verify(mMockDatagramController, times(1)).setDemoMode(eq(false));
+        verify(mMockSatelliteSessionController, times(1)).onSatelliteModemStateChanged(
+                eq(SATELLITE_MODEM_STATE_OFF));
 
         clearInvocations(mMockSatelliteSessionController);
         clearInvocations(mMockDatagramController);
-        sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_CONNECTED, null);
+        mSatelliteControllerUT.isSatelliteBeingDisabled = true;
+        sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_NOT_CONNECTED, null);
         processAllMessages();
         verify(mMockSatelliteSessionController, times(1)).onSatelliteModemStateChanged(
-                SATELLITE_MODEM_STATE_CONNECTED);
+                SATELLITE_MODEM_STATE_NOT_CONNECTED);
+
+        clearInvocations(mMockSatelliteSessionController);
+        mSatelliteControllerUT.isSatelliteBeingDisabled = false;
+        sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_NOT_CONNECTED, null);
+        processAllMessages();
+        verify(mMockSatelliteSessionController, never()).onSatelliteModemStateChanged(
+                SATELLITE_MODEM_STATE_NOT_CONNECTED);
     }
 
     @Test
     public void testRequestNtnSignalStrengthWithFeatureFlagEnabled() {
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
-
         resetSatelliteControllerUT();
 
         mRequestNtnSignalStrengthSemaphore.drainPermits();
@@ -2410,7 +2711,7 @@
         assertEquals(expectedLevel, signalStrength[0].getLevel());
         verifyRequestNtnSignalStrength(NTN_SIGNAL_STRENGTH_POOR, SATELLITE_RESULT_SUCCESS);
 
-        mSatelliteControllerUT.unregisterForNtnSignalStrengthChanged(SUB_ID, callback);
+        mSatelliteControllerUT.unregisterForNtnSignalStrengthChanged(callback);
         sendNtnSignalStrengthChangedEvent(NTN_SIGNAL_STRENGTH_GREAT, null);
         processAllMessages();
         assertFalse(waitForForEvents(
@@ -2724,21 +3025,18 @@
                     }
                 };
 
-        int errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
-                callback);
+        int errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(callback);
         assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, errorCode);
 
         setUpResponseForRequestIsSatelliteSupported(false,
                 SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
-                callback);
+        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(callback);
         assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, errorCode);
 
         resetSatelliteControllerUT();
         provisionSatelliteService();
-        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
-                callback);
+        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(callback);
         assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
         SatelliteCapabilities expectedCapabilities = mSatelliteCapabilities;
         sendSatelliteCapabilitiesChangedEvent(expectedCapabilities, null);
@@ -2754,7 +3052,7 @@
                 semaphore, 1, "testRegisterForSatelliteCapabilitiesChanged"));
         assertTrue(expectedCapabilities.equals(satelliteCapabilities[0]));
 
-        mSatelliteControllerUT.unregisterForCapabilitiesChanged(SUB_ID, callback);
+        mSatelliteControllerUT.unregisterForCapabilitiesChanged(callback);
         expectedCapabilities = mSatelliteCapabilities;
         sendSatelliteCapabilitiesChangedEvent(expectedCapabilities, null);
         processAllMessages();
@@ -2783,22 +3081,19 @@
                     }
                 };
 
-        int errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
-                callback);
+        int errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(callback);
         assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, errorCode);
 
         setUpResponseForRequestIsSatelliteSupported(false,
                 SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(false, SATELLITE_RESULT_NOT_SUPPORTED);
-        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
-                callback);
+        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(callback);
         assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, errorCode);
 
         resetSatelliteControllerUT();
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(false, SATELLITE_RESULT_NOT_SUPPORTED);
-        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(SUB_ID,
-                callback);
+        errorCode = mSatelliteControllerUT.registerForCapabilitiesChanged(callback);
         assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, errorCode);
 
         SatelliteCapabilities expectedCapabilities = mSatelliteCapabilities;
@@ -2885,8 +3180,8 @@
         List<String> barredPlmnList = new ArrayList<>();
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
                 entitlementPlmnList, barredPlmnList, mIIntegerConsumer);
-        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(anyBoolean(),
-                anyBoolean(), anyBoolean(), any(Message.class));
+        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(
+                any(SatelliteModemEnableRequestAttributes.class), any(Message.class));
 
         // If the entitlement plmn list and the overlay config plmn list are available and the
         // carrier plmn list and the barred plmn list are empty, verify passing to the modem.
@@ -2943,32 +3238,32 @@
         entitlementPlmnList = Arrays.stream(new String[]{"00101", "00102", ""}).toList();
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
                 entitlementPlmnList, barredPlmnList, mIIntegerConsumer);
-        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(anyBoolean(),
-                anyBoolean(), anyBoolean(), any(Message.class));
+        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(
+                any(SatelliteModemEnableRequestAttributes.class), any(Message.class));
 
         // If the entitlement plmn list is invalid, verify not passing to the modem.
         reset(mMockSatelliteModemInterface);
         entitlementPlmnList = Arrays.stream(new String[]{"00101", "00102", "123456789"}).toList();
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
                 entitlementPlmnList, barredPlmnList, mIIntegerConsumer);
-        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(anyBoolean(),
-                anyBoolean(), anyBoolean(), any(Message.class));
+        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(
+                any(SatelliteModemEnableRequestAttributes.class), any(Message.class));
 
         // If the entitlement plmn list is invalid, verify not passing to the modem.
         reset(mMockSatelliteModemInterface);
         entitlementPlmnList = Arrays.stream(new String[]{"00101", "00102", "12"}).toList();
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
                 entitlementPlmnList, barredPlmnList, mIIntegerConsumer);
-        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(anyBoolean(),
-                anyBoolean(), anyBoolean(), any(Message.class));
+        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(
+                any(SatelliteModemEnableRequestAttributes.class), any(Message.class));
 
         // If the entitlement plmn list is invalid, verify not passing to the modem.
         reset(mMockSatelliteModemInterface);
         entitlementPlmnList = Arrays.stream(new String[]{"00101", "00102", "1234"}).toList();
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
                 entitlementPlmnList, barredPlmnList, mIIntegerConsumer);
-        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(anyBoolean(),
-                anyBoolean(), anyBoolean(), any(Message.class));
+        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(
+                any(SatelliteModemEnableRequestAttributes.class), any(Message.class));
     }
 
     private void verifyPassingToModemAfterQueryCompleted(List<String> entitlementPlmnList,
@@ -3023,7 +3318,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
@@ -3032,13 +3328,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());
 
@@ -3048,15 +3347,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());
     }
@@ -3334,14 +3637,17 @@
     }
 
     @Test
-    public void testHandleEventServiceStateChanged() throws Exception {
+    public void testHandleEventServiceStateChanged() {
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).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();
         processAllMessages();
-        assertEquals(false,
-                mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false));
+        assertFalse(mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false));
         verify(mMockNotificationManager, never()).notifyAsUser(anyString(), anyInt(), any(), any());
 
         // Check sending a system notification when the satellite is connected
@@ -3350,13 +3656,14 @@
         processAllMessages();
         verify(mMockNotificationManager, times(1)).notifyAsUser(anyString(), anyInt(), any(),
                 any());
-        assertEquals(true,
-                mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false));
+        // Just by showing notification we do not update the pref file , only once user interact
+        // only we will update the pref value to true.
+        assertFalse(mSharedPreferences.getBoolean(SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY, false));
 
         // Check don't display again after displayed already a system notification.
         sendServiceStateChangedEvent();
         processAllMessages();
-        verify(mMockNotificationManager, times(1)).notifyAsUser(anyString(), anyInt(), any(),
+        verify(mMockNotificationManager, times(2)).notifyAsUser(anyString(), anyInt(), any(),
                 any());
     }
 
@@ -3366,38 +3673,44 @@
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        sendProvisionedStateChangedEvent(true, null);
+        setProvisionedState(true);
         processAllMessages();
         verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
 
         // Successfully disable satellite
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(false, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
         verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
 
         // Time out to enable satellite
+        ArgumentCaptor<SatelliteModemEnableRequestAttributes> enableSatelliteRequest =
+                ArgumentCaptor.forClass(SatelliteModemEnableRequestAttributes.class);
         ArgumentCaptor<Message> enableSatelliteResponse = ArgumentCaptor.forClass(Message.class);
         mIIntegerConsumerResults.clear();
         setUpNoResponseForRequestSatelliteEnabled(true, false, false);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        clearInvocations(mMockSatelliteModemInterface);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
-        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(eq(true), eq(false), eq(false),
+        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(
+                enableSatelliteRequest.capture(),
                 enableSatelliteResponse.capture());
+        SatelliteModemEnableRequestAttributes request = enableSatelliteRequest.getValue();
+        assertTrue(request.isEnabled());
+        assertFalse(request.isDemoMode());
+        assertFalse(request.isEmergencyMode());
 
         clearInvocations(mMockSatelliteModemInterface);
         moveTimeForward(TEST_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMEOUT_MILLIS);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_MODEM_TIMEOUT, (long) mIIntegerConsumerResults.get(0));
-        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(eq(false), eq(false),
-                eq(false), any(Message.class));
+        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(
+                any(SatelliteModemEnableRequestAttributes.class), any(Message.class));
         verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
 
         // Send the response for the above request to enable satellite. SatelliteController should
@@ -3411,8 +3724,7 @@
         // Successfully enable satellite
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -3423,12 +3735,11 @@
         mIIntegerConsumerResults.clear();
         clearInvocations(mMockSatelliteModemInterface);
         setUpNoResponseForRequestSatelliteEnabled(false, false, false);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(false, false, false, mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
-        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(eq(false), eq(false),
-                eq(false),
+        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(
+                any(SatelliteModemEnableRequestAttributes.class),
                 disableSatelliteResponse.capture());
 
         clearInvocations(mMockSatelliteModemInterface);
@@ -3436,9 +3747,10 @@
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_MODEM_TIMEOUT, (long) mIIntegerConsumerResults.get(0));
-        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(anyBoolean(),
-                anyBoolean(), anyBoolean(), any(Message.class));
-        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(
+                any(SatelliteModemEnableRequestAttributes.class), any(Message.class));
+        // Satellite should state at enabled state since satellite disable request failed
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
 
         // Send the response for the above request to disable satellite. SatelliteController should
         // ignore the event
@@ -3446,7 +3758,7 @@
         AsyncResult.forMessage(response, null, null);
         response.sendToTarget();
         processAllMessages();
-        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
     }
 
     @Test
@@ -3462,9 +3774,9 @@
         doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        sendProvisionedStateChangedEvent(true, null);
+        setProvisionedState(true);
         setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        sendProvisionedStateChangedEvent(true, null);
+        setProvisionedState(true);
         processAllMessages();
         verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
 
@@ -3472,8 +3784,7 @@
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
         mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
         setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -3488,8 +3799,7 @@
         // Ignore request ntn signal strength for redundant enable request
         reset(mMockSatelliteModemInterface);
         doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -3502,8 +3812,7 @@
         reset(mMockSatelliteModemInterface);
         doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
         setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(false, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -3517,8 +3826,7 @@
         doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(false, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -3533,8 +3841,7 @@
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
         mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
         setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -3571,8 +3878,7 @@
         resetSatelliteControllerUT();
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        int errorCode = mSatelliteControllerUT.registerForSatelliteSupportedStateChanged(
-                SUB_ID, callback);
+        int errorCode = mSatelliteControllerUT.registerForSatelliteSupportedStateChanged(callback);
         assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
 
         sendSatelliteSupportedStateChangedEvent(true, null);
@@ -3606,13 +3912,12 @@
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
 
         // Successfully enable satellite
-        sendProvisionedStateChangedEvent(true, null);
+        setProvisionedState(true);
         processAllMessages();
         verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -3635,7 +3940,7 @@
         // Verify satellite was disabled
         verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
 
-        mSatelliteControllerUT.unregisterForSatelliteSupportedStateChanged(SUB_ID, callback);
+        mSatelliteControllerUT.unregisterForSatelliteSupportedStateChanged(callback);
         sendSatelliteSupportedStateChangedEvent(true, null);
         processAllMessages();
         assertFalse(waitForForEvents(
@@ -3661,7 +3966,7 @@
                     }
                 };
         int errorCode = mSatelliteControllerUT.registerForSatelliteSupportedStateChanged(
-                SUB_ID, callback);
+                callback);
         assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, errorCode);
     }
 
@@ -3764,17 +4069,956 @@
                 mSatelliteControllerUT.getCarrierEmergencyCallWaitForConnectionTimeoutMillis());
     }
 
+    @Test
+    public void testIsCarrierRoamingNtnEligible() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(false);
+        assertFalse(mSatelliteControllerUT.isCarrierRoamingNtnEligible(null));
+
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState2.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        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();
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                "00102", supportedServices2);
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                "00103", supportedServices3);
+        mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager
+                        .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                carrierSupportedSatelliteServicesPerProvider);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        mSatelliteControllerUT.setSatellitePhone(1);
+        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() {
+        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);
+        mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
+        mCarrierConfigBundle.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 1);
+        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();
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                "00102", supportedServices2);
+        carrierSupportedSatelliteServicesPerProvider.putIntArray(
+                "00103", supportedServices3);
+        mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager
+                        .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
+                carrierSupportedSatelliteServicesPerProvider);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        mSatelliteControllerUT.setSatellitePhone(1);
+        mSatelliteControllerUT.isSatelliteAllowedCallback = null;
+        processAllMessages();
+        mSatelliteControllerUT.elapsedRealtime = 0;
+        assertTrue(mSatelliteControllerUT.isCarrierRoamingNtnEligible(mPhone));
+        verify(mPhone, times(0)).notifyCarrierRoamingNtnEligibleStateChanged(eq(true));
+        verify(mPhone2, times(0)).notifyCarrierRoamingNtnEligibleStateChanged(anyBoolean());
+        clearInvocations(mPhone);
+
+        // 2 minutes later and hysteresis timeout is 1 minute
+        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());
+        verify(mMockNotificationManager, times(1)).notifyAsUser(anyString(), anyInt(), any(),
+                any());
+        clearInvocations(mPhone);
+
+        when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        sendServiceStateChangedEvent();
+        processAllMessages();
+        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();
+        processAllMessages();
+        mSatelliteControllerUT.elapsedRealtime = 0;
+        assertTrue(mSatelliteControllerUT.isCarrierRoamingNtnEligible(mPhone));
+        verify(mPhone, times(0)).notifyCarrierRoamingNtnEligibleStateChanged(eq(true));
+        verify(mPhone2, times(0)).notifyCarrierRoamingNtnEligibleStateChanged(anyBoolean());
+        clearInvocations(mPhone);
+
+        // 2 minutes later and hysteresis timeout is 1 minute
+        mSatelliteControllerUT.elapsedRealtime = 2 * 60 * 1000;
+        moveTimeForward(2 * 60 * 1000);
+        processAllMessages();
+        assertNotNull(mSatelliteControllerUT.isSatelliteAllowedCallback);
+
+        mSatelliteControllerUT.isSatelliteAllowedCallback.onError(new SatelliteException(
+                SATELLITE_RESULT_ERROR));
+        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());
+    }
+
+    @Test
+    public void testGetWwanIsInService() {
+        when(mServiceState.getNetworkRegistrationInfoListForTransportType(
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+                .thenReturn(new ArrayList<>());
+        assertFalse(mSatelliteControllerUT.getWwanIsInService(mServiceState));
+
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+                .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_ROAMING)
+                .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)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfoListForTransportType(
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+                .thenReturn(List.of(nri));
+        assertFalse(mSatelliteControllerUT.getWwanIsInService(mServiceState));
+    }
+
+    @Test
+    public void testRegistrationFailureCallback() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        Semaphore semaphore = new Semaphore(0);
+        final int[] resultErrorCode = 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);
+                resultErrorCode[0] = causeCode;
+                semaphore.release();
+            }
+
+            @Override
+            public void onTerrestrialNetworkAvailableChanged(boolean isAvailable) {
+                logd("onTerrestrialNetworkAvailableChanged: isAvailable=" + isAvailable);
+            }
+        };
+        resetSatelliteControllerUTToSupportedAndProvisionedState();
+        mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
+
+        int RegisterErrorCode = mSatelliteControllerUT.registerForSatelliteModemStateChanged(
+                callback);
+        assertEquals(SATELLITE_RESULT_SUCCESS, RegisterErrorCode);
+        verify(mMockSatelliteSessionController).registerForSatelliteModemStateChanged(callback);
+
+        int expectedErrorCode = 100;
+        mIIntegerConsumerResults.clear();
+        sendSatelliteRegistrationFailureEvent(100, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegistrationFailureCallback"));
+        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);
+    private ResultReceiver mProvisionSatelliteReceiver = new ResultReceiver(null) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mProvisionSateResultCode = resultCode;
+            logd("mProvisionSatelliteReceiver: resultCode=" + resultCode);
+            if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                if (resultData.containsKey(KEY_PROVISION_SATELLITE_TOKENS)) {
+                    mProvisionState = resultData.getBoolean(KEY_PROVISION_SATELLITE_TOKENS);
+                    logd("mProvisionSatelliteReceiver: mProvisionState=" + mProvisionState);
+                } else {
+                    loge("KEY_PROVISION_SATELLITE_TOKENS does not exist.");
+                    mProvisionState = false;
+                }
+            } else {
+                mProvisionState = false;
+            }
+            try {
+                mProvisionSateSemaphore.release();
+            } catch (Exception ex) {
+                loge("mProvisionSatelliteReceiver: Got exception in releasing semaphore, ex=" + ex);
+            }
+        }
+    };
+
+    private List<SatelliteSubscriberProvisionStatus>
+            mRequestSatelliteSubscriberProvisionStatusResultList = new ArrayList<>();
+    private int mRequestSatelliteSubscriberProvisionStatusResultCode = SATELLITE_RESULT_SUCCESS;
+    private Semaphore mRequestSatelliteSubscriberProvisionStatusSemaphore = new Semaphore(0);
+    private ResultReceiver mRequestSatelliteSubscriberProvisionStatusReceiver = new ResultReceiver(
+            null) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            mRequestSatelliteSubscriberProvisionStatusResultCode = resultCode;
+            logd("mRequestSatelliteSubscriberProvisionStatusReceiver: resultCode=" + resultCode);
+            if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                if (resultData.containsKey(KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN)) {
+                    mRequestSatelliteSubscriberProvisionStatusResultList =
+                            resultData.getParcelableArrayList(
+                                    KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN,
+                                    SatelliteSubscriberProvisionStatus.class);
+                } else {
+                    loge("KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN does not exist.");
+                    mRequestSatelliteSubscriberProvisionStatusResultList = new ArrayList<>();
+                }
+            } else {
+                mRequestSatelliteSubscriberProvisionStatusResultList = new ArrayList<>();
+            }
+            try {
+                mRequestSatelliteSubscriberProvisionStatusSemaphore.release();
+            } catch (Exception ex) {
+                loge("mRequestSatelliteSubscriberProvisionStatusReceiver: Got exception in "
+                        + "releasing "
+                        + "semaphore, ex=" + ex);
+            }
+        }
+    };
+
+    @Test
+    public void testRequestSatelliteSubscriberProvisionStatus() throws Exception {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        verifyRequestSatelliteSubscriberProvisionStatus();
+    }
+
+    private void verifyRequestSatelliteSubscriberProvisionStatus() throws Exception {
+        setSatelliteSubscriberTesting();
+        List<SatelliteSubscriberInfo> list = getExpectedSatelliteSubscriberInfoList();
+        mCarrierConfigBundle.putString(KEY_SATELLITE_NIDD_APN_NAME_STRING, mNiddApn);
+        mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ESOS_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)
+            );
+        }
+        moveTimeForward(TimeUnit.MINUTES.toMillis(1));
+        processAllMessages();
+
+        // Verify that calling requestSatelliteSubscriberProvisionStatus returns the expected
+        // list of SatelliteSubscriberProvisionStatus.
+        mSatelliteControllerUT.requestSatelliteSubscriberProvisionStatus(
+                mRequestSatelliteSubscriberProvisionStatusReceiver);
+        moveTimeForward(TimeUnit.MINUTES.toMillis(1));
+        processAllMessages();
+        assertEquals(SATELLITE_RESULT_SUCCESS,
+                mRequestSatelliteSubscriberProvisionStatusResultCode);
+        assertEquals(list.get(0), mRequestSatelliteSubscriberProvisionStatusResultList.get(
+                0).getSatelliteSubscriberInfo());
+    }
+
+    @Test
+    public void testProvisionSatellite() throws Exception {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        verifyRequestSatelliteSubscriberProvisionStatus();
+        List<SatelliteSubscriberInfo> inputList = getExpectedSatelliteSubscriberInfoList();
+        verifyProvisionSatellite(inputList);
+    }
+
+    private void verifyProvisionSatellite(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.provisionSatellite(inputList, mProvisionSatelliteReceiver);
+        processAllMessages();
+        assertEquals(SATELLITE_RESULT_SUCCESS, mProvisionSateResultCode);
+        assertTrue(mProvisionState);
+    }
+
+    @Test
+    public void testRegisterForSatelliteSubscriptionProvisionStateChanged() throws Exception {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+
+        Semaphore semaphore = new Semaphore(0);
+        SatelliteSubscriberProvisionStatus[] resultArray =
+                new SatelliteSubscriberProvisionStatus[2];
+        ISatelliteProvisionStateCallback callback = new ISatelliteProvisionStateCallback.Stub() {
+            @Override
+            public void onSatelliteProvisionStateChanged(boolean provisioned) {
+                logd("onSatelliteProvisionStateChanged: provisioned=" + provisioned);
+            }
+
+            @Override
+            public void onSatelliteSubscriptionProvisionStateChanged(
+                    List<SatelliteSubscriberProvisionStatus> satelliteSubscriberProvisionStatus) {
+                logd("onSatelliteSubscriptionProvisionStateChanged: "
+                        + satelliteSubscriberProvisionStatus);
+                for (int i = 0; i < satelliteSubscriberProvisionStatus.size(); i++) {
+                    resultArray[i] = satelliteSubscriberProvisionStatus.get(i);
+                }
+                try {
+                    semaphore.release();
+                } catch (Exception ex) {
+                    loge("onSatelliteSubscriptionProvisionStateChanged: Got exception in releasing "
+                            + "semaphore, ex=" + ex);
+                }
+            }
+        };
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        int errorCode = mSatelliteControllerUT.registerForSatelliteProvisionStateChanged(callback);
+        processAllMessages();
+        assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
+
+        verifyRequestSatelliteSubscriberProvisionStatus();
+
+        // Verify that onSatelliteSubscriptionProvisionStateChanged is called when requesting
+        // provisioning for the first time.
+        List<SatelliteSubscriberInfo> list = getExpectedSatelliteSubscriberInfoList();
+        List<SatelliteSubscriberInfo> inputList = new ArrayList<>();
+        inputList.add(list.get(0));
+        verifyProvisionSatellite(inputList);
+
+        verify(mMockSatelliteModemInterface, times(1)).updateSatelliteSubscription(anyString(),
+                any());
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteSubscriptionProvisionStateChanged"));
+        assertTrue(resultArray[0].getProvisionStatus());
+        assertEquals(mSubscriberId, resultArray[0].getSatelliteSubscriberInfo().getSubscriberId());
+
+        // Request provisioning with SatelliteSubscriberInfo that has not been provisioned
+        // before, and verify that onSatelliteSubscriptionProvisionStateChanged is called.
+        inputList = new ArrayList<>();
+        inputList.add(list.get(1));
+        verifyProvisionSatellite(inputList);
+
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteSubscriptionProvisionStateChanged"));
+        assertTrue(resultArray[1].getProvisionStatus());
+        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);
+
+        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].getProvisionStatus());
+        assertEquals(mSubscriberId2, resultArray[1].getSatelliteSubscriberInfo().getSubscriberId());
+        assertTrue(resultArray[0].getProvisionStatus());
+        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].getProvisionStatus());
+        assertEquals(mSubscriberId2, resultArray[1].getSatelliteSubscriberInfo().getSubscriberId());
+        assertFalse(resultArray[0].getProvisionStatus());
+        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].getProvisionStatus());
+        assertEquals(mSubscriberId2, resultArray[1].getSatelliteSubscriberInfo().getSubscriberId());
+        assertFalse(resultArray[0].getProvisionStatus());
+        assertEquals(mSubscriberId, resultArray[0].getSatelliteSubscriberInfo().getSubscriberId());
+    }
+
+    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() throws Exception {
+        doReturn("123").when(mContext).getAttributionTag();
+        final int carrierId = 0;
+        SubscriptionInfo subscriptionInfo = new SubscriptionInfo.Builder()
+                .setId(SUB_ID).setIccId(mIccId).setSimSlotIndex(0).setOnlyNonTerrestrialNetwork(
+                        false).setSatelliteESOSSupported(true).setCarrierId(carrierId).build();
+        SubscriptionInfo subscriptionInfo2 = new SubscriptionInfo.Builder()
+                .setId(SUB_ID1).setIccId(mIccId2).setSimSlotIndex(1).setOnlyNonTerrestrialNetwork(
+                        true).setSatelliteESOSSupported(false).setCarrierId(carrierId).build();
+        List<SubscriptionInfo> allSubInfos = new ArrayList<>();
+        allSubInfos.add(subscriptionInfo);
+        allSubInfos.add(subscriptionInfo2);
+        doReturn(allSubInfos).when(mMockSubscriptionManagerService).getAllSubInfoList(
+                anyString(), anyString());
+        SubscriptionInfoInternal subInfoInternal =
+                new SubscriptionInfoInternal.Builder().setCarrierId(0).setImsi(mImsi).setIccId(
+                        mIccId).build();
+        SubscriptionInfoInternal subInfoInternal2 =
+                new SubscriptionInfoInternal.Builder().setCarrierId(0).setImsi(mImsi2).setIccId(
+                        mIccId2).build();
+        doReturn(subscriptionInfo).when(mMockSubscriptionManagerService).getSubscriptionInfo(
+                eq(SUB_ID));
+        doReturn(subscriptionInfo2).when(mMockSubscriptionManagerService).getSubscriptionInfo(
+                eq(SUB_ID1));
+        Field field = SatelliteController.class.getDeclaredField("mInjectSubscriptionManager");
+        field.setAccessible(true);
+        field.set(mSatelliteControllerUT, mSubscriptionManager);
+        doReturn(mMsisdn).when(mSubscriptionManager).getPhoneNumber(eq(SUB_ID));
+        doReturn(mMsisdn2).when(mSubscriptionManager).getPhoneNumber(eq(SUB_ID1));
+        Field provisionedSubscriberIdField = SatelliteController.class.getDeclaredField(
+                "mProvisionedSubscriberId");
+        provisionedSubscriberIdField.setAccessible(true);
+        provisionedSubscriberIdField.set(mSatelliteControllerUT, new HashMap<>());
+        Field subscriberIdPerSubField = SatelliteController.class.getDeclaredField(
+                "mSubscriberIdPerSub");
+        subscriberIdPerSubField.setAccessible(true);
+        subscriberIdPerSubField.set(mSatelliteControllerUT, new HashMap<>());
+        Field lastConfiguredIccIdField = SatelliteController.class.getDeclaredField(
+                "mLastConfiguredIccId");
+        lastConfiguredIccIdField.setAccessible(true);
+        lastConfiguredIccIdField.set(mSatelliteControllerUT, null);
+        doReturn(subInfoInternal).when(mMockSubscriptionManagerService).getSubscriptionInfoInternal(
+                eq(SUB_ID));
+        doReturn(subInfoInternal2).when(
+                mMockSubscriptionManagerService).getSubscriptionInfoInternal(eq(SUB_ID1));
+        doReturn(mResources).when(mContext).getResources();
+        doReturn("package").when(mResources).getString(
+                eq(R.string.config_satellite_gateway_service_package));
+        doReturn("className").when(mResources).getString(
+                eq(R.string.config_satellite_carrier_roaming_esos_provisioned_class));
+    }
+
+    private List<SatelliteSubscriberInfo> getExpectedSatelliteSubscriberInfoList() {
+        List<SatelliteSubscriberInfo> list = new ArrayList<>();
+        list.add(new SatelliteSubscriberInfo.Builder().setSubscriberId(mSubscriberId).setCarrierId(
+                mCarrierId).setNiddApn(mNiddApn).setSubId(SUB_ID).setSubscriberIdType(
+                SatelliteSubscriberInfo.IMSI_MSISDN).build());
+        list.add(new SatelliteSubscriberInfo.Builder().setSubscriberId(mSubscriberId2).setCarrierId(
+                mCarrierId).setNiddApn(mNiddApn).setSubId(SUB_ID1).setSubscriberIdType(
+                SatelliteSubscriberInfo.ICCID).build());
+        return list;
+    }
+
+    @Test
+    public void testCheckForSubscriberIdChange_noChanged() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+
+        String imsi = "012345";
+        String oldMsisdn = "1234567890";
+        String newMsisdn = "1234567890";
+        List<SubscriptionInfo> allSubInfos = new ArrayList<>();
+        Optional<String> getSubscriberId;
+        SubscriptionInfoInternal subInfoInternal =
+                new SubscriptionInfoInternal.Builder().setCarrierId(0)
+                        .setImsi(imsi).build();
+
+        when(mSubscriptionInfo.getSubscriptionId()).thenReturn(SUB_ID);
+        allSubInfos.add(mSubscriptionInfo);
+        doReturn(" ").when(mContext).getOpPackageName();
+        doReturn(" ").when(mContext).getAttributionTag();
+        when(mMockSubscriptionManagerService.getAllSubInfoList(anyString(), anyString()))
+                .thenReturn(allSubInfos);
+        when(mSubscriptionInfo.isSatelliteESOSSupported()).thenReturn(true);
+        when(mMockSubscriptionManagerService.getSubscriptionInfoInternal(SUB_ID))
+                .thenReturn(subInfoInternal);
+
+        try {
+            Field field = SatelliteController.class.getDeclaredField("mInjectSubscriptionManager");
+            field.setAccessible(true);
+            field.set(mSatelliteControllerUT, mSubscriptionManager);
+        } catch (Exception e) {
+            loge("Exception InjectSubscriptionManager e: " + e);
+        }
+        when(mSubscriptionManager.getPhoneNumber(SUB_ID)).thenReturn(newMsisdn);
+        when(mSubscriptionInfo.isOnlyNonTerrestrialNetwork()).thenReturn(false);
+        mSatelliteControllerUT.subscriberIdPerSub().put(imsi + oldMsisdn, SUB_ID);
+
+        getSubscriberId = mSatelliteControllerUT.subscriberIdPerSub().entrySet().stream()
+                .filter(entry -> entry.getValue().equals(SUB_ID))
+                .map(Map.Entry::getKey).findFirst();
+        assertEquals(imsi + newMsisdn, getSubscriberId.get());
+
+        setComponentName();
+        mSatelliteControllerUT.subsInfoListPerPriority().computeIfAbsent(
+                        getKeyPriority(mSubscriptionInfo), k -> new ArrayList<>())
+                .add(mSubscriptionInfo);
+        mSatelliteControllerUT.evaluateESOSProfilesPrioritizationTest();
+        // Verify that broadcast has not been sent.
+        verify(mContext, times(0)).sendBroadcast(any(Intent.class));
+    }
+
+    @Test
+    public void testCheckForSubscriberIdChange_changed() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        List<SubscriptionInfo> allSubInfos = new ArrayList<>();
+
+        String imsi = "012345";
+        String oldMsisdn = "1234567890";
+        String newMsisdn = "4567891234";
+
+        Optional<String> getSubscriberId;
+        SubscriptionInfoInternal subInfoInternal =
+                new SubscriptionInfoInternal.Builder().setCarrierId(0).setImsi(imsi).build();
+
+        when(mSubscriptionInfo.getSubscriptionId()).thenReturn(SUB_ID);
+        allSubInfos.add(mSubscriptionInfo);
+        doReturn(" ").when(mContext).getOpPackageName();
+        doReturn(" ").when(mContext).getAttributionTag();
+        when(mMockSubscriptionManagerService.getAllSubInfoList(anyString(), anyString()))
+                .thenReturn(allSubInfos);
+
+        when(mSubscriptionInfo.isSatelliteESOSSupported()).thenReturn(true);
+        when(mSubscriptionInfo.isActive()).thenReturn(true);
+        when(mMockSubscriptionManagerService.getSubscriptionInfoInternal(SUB_ID))
+                .thenReturn(subInfoInternal);
+
+        try {
+            Field field = SatelliteController.class.getDeclaredField("mInjectSubscriptionManager");
+            field.setAccessible(true);
+            field.set(mSatelliteControllerUT, mSubscriptionManager);
+        } catch (Exception e) {
+            loge("Exception InjectSubscriptionManager e: " + e);
+        }
+        when(mSubscriptionManager.getPhoneNumber(SUB_ID)).thenReturn(newMsisdn);
+        when(mSubscriptionInfo.isOnlyNonTerrestrialNetwork()).thenReturn(false);
+        mSatelliteControllerUT.subscriberIdPerSub().put(imsi + oldMsisdn, SUB_ID);
+
+        getSubscriberId = mSatelliteControllerUT.subscriberIdPerSub().entrySet().stream()
+                .filter(entry -> entry.getValue().equals(SUB_ID))
+                .map(Map.Entry::getKey).findFirst();
+        assertNotEquals(imsi + newMsisdn, getSubscriberId.get());
+
+        setComponentName();
+        mSatelliteControllerUT.subsInfoListPerPriority().computeIfAbsent(
+                        getKeyPriority(mSubscriptionInfo), k -> new ArrayList<>())
+                .add(mSubscriptionInfo);
+        mSatelliteControllerUT.evaluateESOSProfilesPrioritizationTest();
+        // Verify that broadcast has been sent.
+        verify(mContext, times(1)).sendBroadcast(any(Intent.class));
+    }
+
+    @Test
+    public void testProvisionStatusPerSubscriberIdGetFromDb() throws Exception {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+
+        setSatelliteSubscriberTesting();
+        // Check if the cache is not updated when the value read from the database is false.
+        verifyProvisionStatusPerSubscriberIdGetFromDb(false);
+
+        // Check if the cache is updated when the value read from the database is true.
+        verifyProvisionStatusPerSubscriberIdGetFromDb(true);
+    }
+
+    @Test
+    public void testProvisionStatusPerSubscriberIdStoreToDb() throws Exception {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+
+        setSatelliteSubscriberTesting();
+        // Check if the cache is not updated when the value read from the database is false.
+        verifyProvisionStatusPerSubscriberIdGetFromDb(false);
+
+        List<SatelliteSubscriberInfo> inputList = getExpectedSatelliteSubscriberInfoList();
+        verifyProvisionSatellite(inputList);
+        verify(mMockSubscriptionManagerService).setIsSatelliteProvisionedForNonIpDatagram(
+                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);
+    }
+
+    private void verifyProvisionStatusPerSubscriberIdGetFromDb(boolean provision) {
+        doReturn(provision).when(
+                mMockSubscriptionManagerService).isSatelliteProvisionedForNonIpDatagram(anyInt());
+        mCarrierConfigBundle.putString(KEY_SATELLITE_NIDD_APN_NAME_STRING, mNiddApn);
+        mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ESOS_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)
+            );
+        }
+        moveTimeForward(TimeUnit.MINUTES.toMillis(1));
+        processAllMessages();
+        mSatelliteControllerUT.requestSatelliteSubscriberProvisionStatus(
+                mRequestSatelliteSubscriberProvisionStatusReceiver);
+        moveTimeForward(TimeUnit.MINUTES.toMillis(1));
+        processAllMessages();
+        assertEquals(SATELLITE_RESULT_SUCCESS,
+                mRequestSatelliteSubscriberProvisionStatusResultCode);
+        assertEquals(provision,
+                mRequestSatelliteSubscriberProvisionStatusResultList.get(0).getProvisionStatus());
+    }
+
+    private void setComponentName() {
+        when(mSatelliteControllerUT.getStringFromOverlayConfigTest(
+                R.string.config_satellite_gateway_service_package))
+                .thenReturn("com.example.package");
+        when(mSatelliteControllerUT.getStringFromOverlayConfigTest(
+                R.string.config_satellite_carrier_roaming_esos_provisioned_class))
+                .thenReturn("com.example.class");
+    }
+
+    private int getKeyPriority(SubscriptionInfo subscriptionInfo) {
+        boolean isActive = subscriptionInfo.isActive();
+        boolean isNtnOnly = subscriptionInfo.isOnlyNonTerrestrialNetwork();
+        boolean isESOSSupported = subscriptionInfo.isSatelliteESOSSupported();
+
+        int keyPriority;
+        if (isESOSSupported && isActive) {
+            keyPriority = 1;
+        } else if (isNtnOnly) {
+            keyPriority = 2;
+        } else if (isESOSSupported) {
+            keyPriority = 3;
+        } else {
+            keyPriority = -1;
+        }
+        return keyPriority;
+    }
+
     private void resetSatelliteControllerUTEnabledState() {
         logd("resetSatelliteControllerUTEnabledState");
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         doNothing().when(mMockSatelliteModemInterface)
                 .setSatelliteServicePackageName(anyString());
-        mSatelliteControllerUT.setSatelliteServicePackageName("TestSatelliteService");
+        mSatelliteControllerUT.setSatelliteServicePackageName("TestSatelliteService", null);
         processAllMessages();
 
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        setProvisionedState(false);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        sendProvisionedStateChangedEvent(true, null);
+        setProvisionedState(true);
         processAllMessages();
         verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
     }
@@ -3789,7 +5033,7 @@
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         doNothing().when(mMockSatelliteModemInterface)
                 .setSatelliteServicePackageName(anyString());
-        mSatelliteControllerUT.setSatelliteServicePackageName("TestSatelliteService");
+        mSatelliteControllerUT.setSatelliteServicePackageName("TestSatelliteService", null);
         processAllMessages();
     }
 
@@ -3797,7 +5041,7 @@
         resetSatelliteControllerUT();
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        sendProvisionedStateChangedEvent(true, null);
+        setProvisionedState(true);
         processAllMessages();
         verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
     }
@@ -3806,6 +5050,8 @@
         resetSatelliteControllerUTToSupportedAndProvisionedState();
         // Clean up pending resources and move satellite controller to OFF state.
         sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_UNAVAILABLE, null);
+        mSatelliteControllerUT.moveSatelliteToOffStateAndCleanUpResources(
+                SATELLITE_RESULT_REQUEST_ABORTED);
         processAllMessages();
         verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
     }
@@ -3815,9 +5061,9 @@
         setRadioPower(true);
         processAllMessages();
 
+        mIIntegerConsumerResults.clear();
         setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
-                mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(true, false, false, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -3878,15 +5124,7 @@
 
     private void setUpResponseForRequestIsSatelliteProvisioned(
             boolean isSatelliteProvisioned, @SatelliteManager.SatelliteResult int error) {
-        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
-                ? null : new SatelliteException(error);
-        int[] provisioned = new int[]{isSatelliteProvisioned ? 1 : 0};
-        doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[0];
-            AsyncResult.forMessage(message, provisioned, exception);
-            message.sendToTarget();
-            return null;
-        }).when(mMockSatelliteModemInterface).requestIsSatelliteProvisioned(any(Message.class));
+        mSatelliteControllerUT.setSatelliteProvisioned(isSatelliteProvisioned);
     }
 
     private void setUpResponseForRequestSatelliteEnabled(
@@ -3898,12 +5136,12 @@
             if (exception == null && !enabled) {
                 sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_OFF, null);
             }
-            Message message = (Message) invocation.getArguments()[3];
+            Message message = (Message) invocation.getArguments()[1];
             AsyncResult.forMessage(message, null, exception);
             message.sendToTarget();
             return null;
         }).when(mMockSatelliteModemInterface)
-                .requestSatelliteEnabled(eq(enabled), eq(demoMode), eq(emergency),
+                .requestSatelliteEnabled(any(SatelliteModemEnableRequestAttributes.class),
                         any(Message.class));
     }
 
@@ -3923,41 +5161,13 @@
     private void setUpNoResponseForRequestSatelliteEnabled(boolean enabled, boolean demoMode,
             boolean emergency) {
         doNothing().when(mMockSatelliteModemInterface)
-                .requestSatelliteEnabled(eq(enabled), eq(demoMode), eq(emergency),
+                .requestSatelliteEnabled(eq(new SatelliteModemEnableRequestAttributes(
+                                enabled, demoMode, emergency,
+                                new SatelliteSubscriptionInfo("", "")
+                        )),
                         any(Message.class));
     }
 
-    private void setUpResponseForProvisionSatelliteService(
-            String token, byte[] provisionData, @SatelliteManager.SatelliteResult int error) {
-        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
-                ? null : new SatelliteException(error);
-        doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[2];
-            AsyncResult.forMessage(message, null, exception);
-            message.sendToTarget();
-            return null;
-        }).when(mMockSatelliteModemInterface)
-                .provisionSatelliteService(eq(token), any(byte[].class), any(Message.class));
-    }
-
-    private void setUpNoResponseForProvisionSatelliteService(String token) {
-        doNothing().when(mMockSatelliteModemInterface)
-                .provisionSatelliteService(eq(token), any(), any(Message.class));
-    }
-
-    private void setUpResponseForDeprovisionSatelliteService(String token,
-            @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)
-                .deprovisionSatelliteService(eq(token), any(Message.class));
-    }
-
     private void setUpResponseForRequestSatelliteCapabilities(
             SatelliteCapabilities satelliteCapabilities,
             @SatelliteManager.SatelliteResult int error) {
@@ -4186,7 +5396,7 @@
 
     private void verifySatelliteSupported(boolean supported, int expectedErrorCode) {
         mSatelliteSupportSemaphore.drainPermits();
-        mSatelliteControllerUT.requestIsSatelliteSupported(SUB_ID, mSatelliteSupportReceiver);
+        mSatelliteControllerUT.requestIsSatelliteSupported(mSatelliteSupportReceiver);
         processAllMessages();
         assertTrue(waitForRequestIsSatelliteSupportedResult(1));
         assertEquals(expectedErrorCode, mQueriedSatelliteSupportedResultCode);
@@ -4196,7 +5406,7 @@
     private void verifySatelliteSupported(TestSatelliteController satelliteController,
             boolean supported, int expectedErrorCode) {
         mSatelliteSupportSemaphore.drainPermits();
-        satelliteController.requestIsSatelliteSupported(SUB_ID, mSatelliteSupportReceiver);
+        satelliteController.requestIsSatelliteSupported(mSatelliteSupportReceiver);
         processAllMessages();
         assertTrue(waitForRequestIsSatelliteSupportedResult(1));
         assertEquals(expectedErrorCode, mQueriedSatelliteSupportedResultCode);
@@ -4205,7 +5415,7 @@
 
     private void verifySatelliteEnabled(boolean enabled, int expectedErrorCode) {
         mIsSatelliteEnabledSemaphore.drainPermits();
-        mSatelliteControllerUT.requestIsSatelliteEnabled(SUB_ID, mIsSatelliteEnabledReceiver);
+        mSatelliteControllerUT.requestIsSatelliteEnabled(mIsSatelliteEnabledReceiver);
         processAllMessages();
         assertTrue(waitForRequestIsSatelliteEnabledResult(1));
         assertEquals(expectedErrorCode, mQueriedIsSatelliteEnabledResultCode);
@@ -4214,8 +5424,7 @@
 
     private void verifySatelliteProvisioned(boolean provisioned, int expectedErrorCode) {
         mIsSatelliteProvisionedSemaphore.drainPermits();
-        mSatelliteControllerUT.requestIsSatelliteProvisioned(
-                SUB_ID, mIsSatelliteProvisionedReceiver);
+        mSatelliteControllerUT.requestIsSatelliteProvisioned(mIsSatelliteProvisionedReceiver);
         processAllMessages();
         assertTrue(waitForRequestIsSatelliteProvisionedResult(1));
         assertEquals(expectedErrorCode, mQueriedIsSatelliteProvisionedResultCode);
@@ -4226,18 +5435,15 @@
             @NtnSignalStrength.NtnSignalStrengthLevel int signalStrengthLevel,
             int expectedErrorCode) {
         mRequestNtnSignalStrengthSemaphore.drainPermits();
-        mSatelliteControllerUT.requestNtnSignalStrength(SUB_ID, mRequestNtnSignalStrengthReceiver);
+        mSatelliteControllerUT.requestNtnSignalStrength(mRequestNtnSignalStrengthReceiver);
         processAllMessages();
         assertTrue(waitForRequestNtnSignalStrengthResult(1));
         assertEquals(expectedErrorCode, mQueriedNtnSignalStrengthResultCode);
         assertEquals(signalStrengthLevel, mQueriedNtnSignalStrengthLevel);
     }
 
-    private void sendProvisionedStateChangedEvent(boolean provisioned, Throwable exception) {
-        Message msg = mSatelliteControllerUT.obtainMessage(
-                26 /* EVENT_SATELLITE_PROVISION_STATE_CHANGED */);
-        msg.obj = new AsyncResult(null, provisioned, exception);
-        msg.sendToTarget();
+    private void setProvisionedState(@Nullable Boolean provisioned) {
+        mSatelliteControllerUT.setSatelliteProvisioned(provisioned);
     }
 
     private void sendSatelliteModemStateChangedEvent(int state, Throwable exception) {
@@ -4293,6 +5499,21 @@
         msg.sendToTarget();
     }
 
+    private void sendSatelliteRegistrationFailureEvent(int errorCode, Throwable exception) {
+        Message msg = mSatelliteControllerUT.obtainMessage(
+                54 /* EVENT_SATELLITE_REGISTRATION_FAILURE */);
+        msg.obj = new AsyncResult(null, errorCode, exception);
+        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 setRadioPower(boolean on) {
         mSimulatedCommands.setRadioPower(on, false, false, null);
     }
@@ -4301,14 +5522,13 @@
             INtnSignalStrengthCallback callback, int expectedError) {
         if (expectedError == SATELLITE_RESULT_SUCCESS) {
             try {
-                mSatelliteControllerUT.registerForNtnSignalStrengthChanged(subId, callback);
+                mSatelliteControllerUT.registerForNtnSignalStrengthChanged(callback);
             } catch (RemoteException ex) {
                 throw new AssertionError();
             }
         } else {
             RemoteException ex = assertThrows(RemoteException.class,
-                    () -> mSatelliteControllerUT.registerForNtnSignalStrengthChanged(subId,
-                            callback));
+                    () -> mSatelliteControllerUT.registerForNtnSignalStrengthChanged(callback));
             assertTrue("The cause is not IllegalStateException",
                     ex.getCause() instanceof IllegalStateException);
         }
@@ -4323,9 +5543,16 @@
         setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForProvisionSatelliteService(TEST_SATELLITE_TOKEN, testProvisionData,
-                SATELLITE_RESULT_SUCCESS);
-        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(SUB_ID,
+
+        try {
+            setSatelliteSubscriberTesting();
+        } catch (Exception ex) {
+            fail("provisionSatelliteService.setSatelliteSubscriberTesting: ex=" + ex);
+        }
+        doReturn(true).when(mMockSubscriptionManagerService).isSatelliteProvisionedForNonIpDatagram(
+                anyInt());
+
+        cancelRemote = mSatelliteControllerUT.provisionSatelliteService(
                 TEST_SATELLITE_TOKEN,
                 testProvisionData, mIIntegerConsumer);
         processAllMessages();
@@ -4337,8 +5564,7 @@
 
     private void deprovisionSatelliteService() {
         mIIntegerConsumerResults.clear();
-        setUpResponseForDeprovisionSatelliteService(TEST_SATELLITE_TOKEN, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.deprovisionSatelliteService(SUB_ID,
+        mSatelliteControllerUT.deprovisionSatelliteService(
                 TEST_SATELLITE_TOKEN, mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -4490,17 +5716,22 @@
         }
     }
 
-    private static class TestSatelliteController extends SatelliteController {
+    private class TestSatelliteController extends SatelliteController {
         public boolean setSettingsKeyForSatelliteModeCalled = false;
         public boolean allRadiosDisabled = true;
         public long elapsedRealtime = 0;
         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;
 
         TestSatelliteController(
                 Context context, Looper looper, @NonNull FeatureFlags featureFlags) {
             super(context, looper, featureFlags);
             logd("Constructing TestSatelliteController");
+            isApplicationUpdated = false;
         }
 
         @Override
@@ -4523,6 +5754,7 @@
 
         @Override
         protected boolean areAllRadiosDisabled() {
+            logd("areAllRadiosDisabled: " + allRadiosDisabled);
             return allRadiosDisabled;
         }
 
@@ -4541,5 +5773,98 @@
         void setSatelliteSessionController(SatelliteSessionController satelliteSessionController) {
             mSatelliteSessionController = satelliteSessionController;
         }
+
+        @Override
+        protected void setSatellitePhone(int subId) {
+            logd("setSatellitePhone");
+            synchronized (mSatellitePhoneLock) {
+                mSatellitePhone = mPhone;
+            }
+        }
+
+        @Override
+        protected void requestIsSatelliteCommunicationAllowedForCurrentLocation(
+                @NonNull OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> callback) {
+            logd("requestIsSatelliteCommunicationAllowedForCurrentLocation: callback="
+                    + callback);
+            isSatelliteAllowedCallback = callback;
+        }
+
+        @Override
+        protected boolean isSubscriptionProvisioned(int subId) {
+            synchronized (mSatellitePhoneLock) {
+                if (mSatellitePhone.getSubId() == subId) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @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;
+        }
+
+        protected String getConfigSatelliteGatewayServicePackage() {
+            String packageName = "com.example.app";
+            return packageName;
+        }
+
+        @Override
+        protected void handleCarrierRoamingNtnAvailableServicesChanged(int subId) {
+            isApplicationUpdated = true;
+        }
+
+        void setSatelliteProvisioned(@Nullable Boolean isProvisioned) {
+            synchronized (mDeviceProvisionLock) {
+                mIsDeviceProvisioned = isProvisioned;
+            }
+        }
+
+        public boolean isRadioOn() {
+            synchronized (mIsRadioOnLock) {
+                return mIsRadioOn;
+            }
+        }
+
+        public boolean isRadioOffRequested() {
+            synchronized (mIsRadioOnLock) {
+                return mRadioOffRequested;
+            }
+        }
+
+        public boolean isWaitForCellularModemOffTimerStarted() {
+            return hasMessages(EVENT_WAIT_FOR_CELLULAR_MODEM_OFF_TIMED_OUT);
+        }
+
+        public Map<String, Integer> subscriberIdPerSub() {
+            synchronized (mSatelliteTokenProvisionedLock) {
+                return mSubscriberIdPerSub;
+            }
+        }
+
+        public Map<Integer, List<SubscriptionInfo>> subsInfoListPerPriority() {
+            synchronized (mSatelliteTokenProvisionedLock) {
+                return mSubsInfoListPerPriority;
+            }
+        }
+
+        public void evaluateESOSProfilesPrioritizationTest() {
+            evaluateESOSProfilesPrioritization();
+        }
+
+        public String getStringFromOverlayConfigTest(int resourceId) {
+            return getStringFromOverlayConfig(resourceId);
+        }
+
+        public boolean isAnyWaitForSatelliteEnablingResponseTimerStarted() {
+            return hasMessages(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT);
+        }
     }
 }
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 f4fa48e..2609ffc 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,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+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,11 +42,13 @@
 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.flag.junit.SetFlagsRule;
 import android.telecom.Connection;
 import android.telecom.TelecomManager;
 import android.telephony.BinderCacheManager;
@@ -65,9 +70,12 @@
 import com.android.internal.telephony.PhoneFactory;
 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 org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -92,6 +100,7 @@
     private static final int TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS = 500;
     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 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";
@@ -108,10 +117,14 @@
     private ImsManager.MmTelFeatureConnectionFactory mMmTelFeatureConnectionFactory;
     @Mock
     private FeatureFlags mFeatureFlags;
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private TestConnection mTestConnection;
     private Uri mTestConnectionAddress = Uri.parse("tel:1234");
     private TestSOSMessageRecommender mTestSOSMessageRecommender;
     private ServiceState mServiceState2;
+    @Mock
+    private SatelliteStats mMockSatelliteStats;
 
     @Before
     public void setUp() throws Exception {
@@ -127,6 +140,7 @@
                 R.integer.config_emergency_call_wait_for_connection_timeout_millis))
                 .thenReturn(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
         mTestSatelliteController = new TestSatelliteController(mContext,
                 Looper.myLooper(), mFeatureFlags);
         mTestImsManager = new TestImsManager(
@@ -146,6 +160,10 @@
         when(mServiceState2.getState()).thenReturn(STATE_OUT_OF_SERVICE);
         when(mPhone.isImsRegistered()).thenReturn(false);
         when(mPhone2.isImsRegistered()).thenReturn(false);
+        replaceInstance(SatelliteStats.class, "sInstance", null,
+                mMockSatelliteStats);
+        doNothing().when(mMockSatelliteStats).onSatelliteSosMessageRecommender(
+                any(SatelliteStats.SatelliteSosMessageRecommenderParams.class));
     }
 
     @After
@@ -158,6 +176,8 @@
         testTimeoutBeforeEmergencyCallEnd(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
@@ -170,6 +190,8 @@
         testTimeoutBeforeEmergencyCallEnd(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
@@ -181,6 +203,8 @@
         mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
         testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS, "", "",
                 DEFAULT_HANDOVER_INTENT_ACTION);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -190,6 +214,8 @@
         mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
         testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS, "", "",
                 DEFAULT_HANDOVER_INTENT_ACTION);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
@@ -197,16 +223,17 @@
         when(mServiceState.getState()).thenReturn(STATE_OUT_OF_SERVICE);
         when(mServiceState2.getState()).thenReturn(STATE_OUT_OF_SERVICE);
         mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
-        mTestSOSMessageRecommender.onEmergencyCallStarted(null);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(null, false);
         processAllMessages();
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        verify(mMockSatelliteStats, never()).onSatelliteSosMessageRecommender(any());
     }
 
     private void testTimeoutBeforeEmergencyCallEnd(int expectedHandoverType,
             String expectedPackageName, String expectedClassName, String expectedAction) {
         mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertRegisterForStateChangedEventsTriggered(mPhone, 1,  1);
@@ -232,14 +259,16 @@
         }
         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.setDeviceProvisioned(false);
         mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1);
@@ -259,6 +288,8 @@
         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
@@ -266,7 +297,7 @@
         mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
         mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
         mTestSatelliteController.isOemEnabledSatelliteSupported = false;
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertRegisterForStateChangedEventsTriggered(mPhone, 1, 1);
@@ -295,17 +326,21 @@
     @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
     public void testNetworkStateChangedBeforeTimeout() {
         mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
@@ -330,11 +365,13 @@
         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() {
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
 
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
@@ -350,8 +387,11 @@
         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);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
@@ -380,12 +420,14 @@
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertUnregisterForStateChangedEventsTriggered(mPhone, 2, 2);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 2, 2);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertTrue(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testEmergencyCallRedialBeforeTimeout() {
         mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
@@ -393,7 +435,7 @@
         assertRegisterForStateChangedEventsTriggered(mPhone2, 1, 1);
         assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
 
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
         assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
@@ -419,35 +461,45 @@
         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
     public void testOnEmergencyCallConnectionStateChangedWithWrongCallId() {
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
 
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
@@ -464,12 +516,15 @@
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 1);
+        verify(mMockSatelliteStats, times(1)).onSatelliteSosMessageRecommender(any());
+        assertFalse(mTestSOSMessageRecommender.isDialerNotified());
     }
 
     @Test
     public void testSatelliteNotAllowedInCurrentLocation() {
         mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
         assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
@@ -491,38 +546,42 @@
         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);
-        testSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(true);
+        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;
     }
 
@@ -531,30 +590,94 @@
         // Both OEM and carrier don't support satellite
         mTestSatelliteController.isSatelliteEmergencyMessagingSupportedViaCarrier = false;
         mTestSatelliteController.isOemEnabledSatelliteSupported = false;
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
         assertEquals(0, mTestSOSMessageRecommender.getTimeOutMillis());
 
         // Only OEM support satellite
         mTestSatelliteController.isOemEnabledSatelliteSupported = true;
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        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. Thus, carrier's timeout duration will be used
+        // 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;
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        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.
+        mTestSatelliteController.isSatelliteEmergencyMessagingSupportedViaCarrier = true;
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(true);
+        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.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);
+    }
+
+    @Test
+    public void testGetEmergencyCallToSatelliteHandoverType_OnlySatelliteViaCarrierAvailable() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN);
+
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(true);
+        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);
+    }
+
+    @Test
+    public void testGetEmergencyCallToSatelliteHandoverType_OemAndCarrierNotAvailable() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN);
+
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
+        mTestSatelliteController.mIsDeviceProvisionedForTest = true;
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
+        assertEquals(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS,
+                mTestSOSMessageRecommender.getEmergencyCallToSatelliteHandoverType());
+
+        mTestSatelliteController.setSatelliteConnectedViaCarrierWithinHysteresisTime(false);
+        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);
     }
 
     private void testStopTrackingCallBeforeTimeout(
             @Connection.ConnectionState int connectionState) {
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
 
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
@@ -576,7 +699,7 @@
             @ServiceState.RegState int availableServiceState,
             @ServiceState.RegState int unavailableServiceState) {
         mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
-        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection, false);
         processAllMessages();
         assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
         assertTrue(mTestSOSMessageRecommender.isTimerStarted());
@@ -648,7 +771,7 @@
                 mProvisionStateChangedCallbacks;
         private int mRegisterForSatelliteProvisionStateChangedCalls = 0;
         private int mUnregisterForSatelliteProvisionStateChangedCalls = 0;
-        private Boolean mIsSatelliteViaOemProvisioned = true;
+        private Boolean mIsDeviceProvisionedForTest = true;
         private boolean mIsSatelliteConnectedViaCarrierWithinHysteresisTime = true;
         public boolean isOemEnabledSatelliteSupported = true;
         public boolean isCarrierEnabledSatelliteSupported = true;
@@ -669,8 +792,8 @@
         }
 
         @Override
-        public Boolean isSatelliteViaOemProvisioned() {
-            return mIsSatelliteViaOemProvisioned;
+        public Boolean isDeviceProvisioned() {
+            return mIsDeviceProvisionedForTest;
         }
 
         @Override
@@ -685,21 +808,21 @@
 
         @Override
         @SatelliteManager.SatelliteResult public int registerForSatelliteProvisionStateChanged(
-                int subId, @NonNull ISatelliteProvisionStateCallback callback) {
+                @NonNull ISatelliteProvisionStateCallback callback) {
             mRegisterForSatelliteProvisionStateChangedCalls++;
             Set<ISatelliteProvisionStateCallback> perSubscriptionCallbacks =
-                    mProvisionStateChangedCallbacks.getOrDefault(subId, new HashSet<>());
+                    mProvisionStateChangedCallbacks.getOrDefault(SUB_ID, new HashSet<>());
             perSubscriptionCallbacks.add(callback);
-            mProvisionStateChangedCallbacks.put(subId, perSubscriptionCallbacks);
+            mProvisionStateChangedCallbacks.put(SUB_ID, perSubscriptionCallbacks);
             return SatelliteManager.SATELLITE_RESULT_SUCCESS;
         }
 
         @Override
         public void unregisterForSatelliteProvisionStateChanged(
-                int subId, @NonNull ISatelliteProvisionStateCallback callback) {
+                @NonNull ISatelliteProvisionStateCallback callback) {
             mUnregisterForSatelliteProvisionStateChangedCalls++;
             Set<ISatelliteProvisionStateCallback> perSubscriptionCallbacks =
-                    mProvisionStateChangedCallbacks.get(subId);
+                    mProvisionStateChangedCallbacks.get(SUB_ID);
             if (perSubscriptionCallbacks != null) {
                 perSubscriptionCallbacks.remove(callback);
             }
@@ -725,6 +848,12 @@
             return carrierEmergencyCallWaitForConnectionTimeoutMillis;
         }
 
+        @Override
+        protected List<DeviceState> getSupportedDeviceStates() {
+            return List.of(new DeviceState(new DeviceState.Configuration.Builder(0 /* identifier */,
+                    "DEFAULT" /* name */).build()));
+        }
+
         public void setSatelliteConnectedViaCarrierWithinHysteresisTime(
                 boolean connectedViaCarrier) {
             mIsSatelliteConnectedViaCarrierWithinHysteresisTime = connectedViaCarrier;
@@ -738,14 +867,14 @@
             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(subId);
+                    mProvisionStateChangedCallbacks.get(SUB_ID);
             if (perSubscriptionCallbacks != null) {
                 for (ISatelliteProvisionStateCallback callback : perSubscriptionCallbacks) {
                     try {
@@ -758,6 +887,26 @@
         }
     }
 
+    /**
+     * 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()));
+        }
+    }
+
     private static class TestImsManager extends ImsManager {
 
         private final List<RegistrationManager.RegistrationCallback> mCallbacks;
@@ -831,6 +980,7 @@
                 isSatelliteAllowedCallback = null;
         private ComponentName mSmsAppComponent = new ComponentName(
                 DEFAULT_SATELLITE_MESSAGING_PACKAGE, DEFAULT_SATELLITE_MESSAGING_CLASS);
+        private boolean mIsDialerNotified;
 
         /**
          * Create an instance of SatelliteSOSMessageRecommender.
@@ -859,6 +1009,12 @@
             isSatelliteAllowedCallback = callback;
         }
 
+        @Override
+        protected void reportESosRecommenderDecision(boolean isDialerNotified) {
+            super.reportESosRecommenderDecision(isDialerNotified);
+            mIsDialerNotified = isDialerNotified;
+        }
+
         public boolean isTimerStarted() {
             return hasMessages(EVENT_TIME_OUT);
         }
@@ -874,6 +1030,10 @@
         public long getTimeOutMillis() {
             return mTimeoutMillis;
         }
+
+        public boolean isDialerNotified() {
+            return mIsDialerNotified;
+        }
     }
 
     private static class TestConnection extends Connection {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java
index 0e4adcd..7bf8ab4 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java
@@ -19,19 +19,28 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
 
 import android.os.PersistableBundle;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyTest;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -41,12 +50,23 @@
 @TestableLooper.RunWithLooper
 public class SatelliteServiceUtilsTest extends TelephonyTest {
     private static final String TAG = "SatelliteServiceUtilsTest";
+    private static final int SUB_ID = 0;
+    private static final int SUB_ID1 = 1;
+    @Mock private ServiceState mServiceState2;
 
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         MockitoAnnotations.initMocks(this);
         logd(TAG + " Setup!");
+
+        replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, mPhone2});
+        when(mPhone.getServiceState()).thenReturn(mServiceState);
+        when(mPhone.getSubId()).thenReturn(SUB_ID);
+        when(mPhone.getPhoneId()).thenReturn(0);
+        when(mPhone2.getServiceState()).thenReturn(mServiceState2);
+        when(mPhone2.getSubId()).thenReturn(SUB_ID1);
+        when(mPhone2.getPhoneId()).thenReturn(1);
     }
 
     @After
@@ -119,4 +139,89 @@
         mergedList = SatelliteServiceUtils.mergeStrLists(l1, l2, l3);
         assertEquals(expectedMergedList, mergedList);
     }
+
+    @Test
+    public void testIsCellularAvailable() {
+        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState2.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        assertFalse(SatelliteServiceUtils.isCellularAvailable());
+
+        when(mServiceState.getState()).thenReturn(ServiceState.STATE_EMERGENCY_ONLY);
+        assertTrue(SatelliteServiceUtils.isCellularAvailable());
+
+        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState2.getState()).thenReturn(ServiceState.STATE_EMERGENCY_ONLY);
+        assertTrue(SatelliteServiceUtils.isCellularAvailable());
+
+        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState2.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState2.isEmergencyOnly()).thenReturn(true);
+        assertTrue(SatelliteServiceUtils.isCellularAvailable());
+    }
+
+    @Test
+    public void testIsSatellitePlmn() {
+        int subId = 1;
+
+        when(mSatelliteController.getSatellitePlmnsForCarrier(eq(subId)))
+                .thenReturn(new ArrayList<>());
+        assertFalse(SatelliteServiceUtils.isSatellitePlmn(subId, mServiceState));
+
+        // registered PLMN is null
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setRegisteredPlmn(null)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfoListForTransportType(
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+                .thenReturn(List.of(nri));
+        assertFalse(SatelliteServiceUtils.isSatellitePlmn(subId, mServiceState));
+
+        // cell identity is null
+        when(mSatelliteController.getSatellitePlmnsForCarrier(eq(subId))).thenReturn(
+                List.of("120260"));
+        nri = new NetworkRegistrationInfo.Builder()
+                .setRegisteredPlmn("123456")
+                .setCellIdentity(null)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfoListForTransportType(
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+                .thenReturn(List.of(nri));
+        assertFalse(SatelliteServiceUtils.isSatellitePlmn(subId, mServiceState));
+
+        // mcc and mnc are null
+        when(mCellIdentity.getMccString()).thenReturn(null);
+        when(mCellIdentity.getMncString()).thenReturn(null);
+        nri = new NetworkRegistrationInfo.Builder()
+                .setRegisteredPlmn("123456")
+                .setCellIdentity(mCellIdentity)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfoListForTransportType(
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+                .thenReturn(List.of(nri));
+        assertFalse(SatelliteServiceUtils.isSatellitePlmn(subId, mServiceState));
+
+        // mccmnc equal to satellite PLMN
+        when(mCellIdentity.getMccString()).thenReturn("120");
+        when(mCellIdentity.getMncString()).thenReturn("260");
+        nri = new NetworkRegistrationInfo.Builder()
+                .setRegisteredPlmn("123456")
+                .setCellIdentity(mCellIdentity)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfoListForTransportType(
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+                .thenReturn(List.of(nri));
+        assertTrue(SatelliteServiceUtils.isSatellitePlmn(subId, mServiceState));
+
+        // registered PLMN equal to satellite PLMN
+        when(mCellIdentity.getMccString()).thenReturn("123");
+        when(mCellIdentity.getMncString()).thenReturn("456");
+        nri = new NetworkRegistrationInfo.Builder()
+                .setRegisteredPlmn("120260")
+                .setCellIdentity(mCellIdentity)
+                .build();
+        when(mServiceState.getNetworkRegistrationInfoListForTransportType(
+                eq(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)))
+                .thenReturn(List.of(nri));
+        assertTrue(SatelliteServiceUtils.isSatellitePlmn(subId, mServiceState));
+    }
 }
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 78763d1..96c50b6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
@@ -16,48 +16,76 @@
 
 package com.android.internal.telephony.satellite;
 
+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;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PersistableBundle;
+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.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;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 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;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -79,7 +107,9 @@
     private static final String STATE_LISTENING = "ListeningState";
     private static final String STATE_NOT_CONNECTED = "NotConnectedState";
     private static final String STATE_CONNECTED = "ConnectedState";
-
+    private static final int SCREEN_OFF_INACTIVITY_TIMEOUT_SEC = 30;
+    private static final int P2P_SMS_INACTIVITY_TIMEOUT_SEC = 180;
+    private static final int ESOS_INACTIVITY_TIMEOUT_SEC = 600;
     private TestSatelliteModemInterface mSatelliteModemInterface;
     private TestSatelliteSessionController mTestSatelliteSessionController;
     private TestSatelliteModemStateCallback mTestSatelliteModemStateCallback;
@@ -88,6 +118,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 {
@@ -102,12 +140,22 @@
                 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(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,
@@ -119,6 +167,7 @@
                 mTestSatelliteModemStateCallback);
         assertSuccessfulModemStateChangedCallback(
                 mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        mTestSatelliteSessionController.setAlarmManager(mAlarmManager);
     }
 
     @After
@@ -170,6 +219,708 @@
     }
 
     @Test
+    public void testScreenOffInactivityTimer() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        doNothing().when(mDeviceStateMonitor).registerForScreenStateChanged(
+                eq(mTestSatelliteSessionController.getHandler()), anyInt(), any());
+        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);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        moveToIdleState();
+
+        // SatelliteSessionController should call registerForScreenStateChanged.
+        verify(mDeviceStateMonitor).registerForScreenStateChanged(mHandlerCaptor.capture(),
+                mMsgCaptor.capture(), any());
+
+        // Notify Screen off
+        sendScreenStateChanged(mHandlerCaptor.getValue(), mMsgCaptor.getValue(), false);
+        processAllMessages();
+        clearInvocations(mMockSatelliteController);
+
+        // 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.
+        verify(mMockSatelliteController).requestSatelliteEnabled(
+                eq(false), eq(false), eq(false), any(IIntegerConsumer.Stub.class));
+    }
+
+    @Test
+    public void testScreenOffInactivityTimerStop() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        doNothing().when(mDeviceStateMonitor).registerForScreenStateChanged(
+                eq(mTestSatelliteSessionController.getHandler()), anyInt(), any());
+        // Satellite enabling request is for an emergency.
+        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(true);
+        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);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        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.
+        verify(mDeviceStateMonitor).registerForScreenStateChanged(mHandlerCaptor.capture(),
+                mMsgCaptor.capture(), any());
+
+        // Notify Screen off
+        sendScreenStateChanged(mHandlerCaptor.getValue(), mMsgCaptor.getValue(), false);
+        processAllMessages();
+
+        // 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 clear.
+        verify(mAlarmManager).cancel(eq(mOnAlarmListenerArgumentCaptor.getValue()));
+    }
+
+    @Test
+    public void testP2pSmsInactivityTimer() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+
+        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(false);
+        when(mMockSatelliteController.isSatelliteRoamingP2pSmSSupported(
+                anyInt())).thenReturn(true);
+        when(mMockSatelliteController.isInCarrierRoamingNbIotNtn()).thenReturn(true);
+        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());
+
+        mTestSatelliteSessionController.setDeviceAlignedWithSatellite(true);
+
+        // Verify that the P2P SMS inactivity timer is stopped.
+        assertFalse(mTestSatelliteSessionController.isP2pSmsInActivityTimerStarted());
+
+        mTestSatelliteSessionController.setDeviceAlignedWithSatellite(false);
+
+        // 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();
+
+        // SatelliteSessionController should move to IDLE state.
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+    }
+
+    @Test
+    public void testEsosInactivityTimer() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+
+        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteEsosSupported(anyInt())).thenReturn(true);
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putInt(KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT,
+                ESOS_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 ESOS inactivity timer is started.
+        assertTrue(mTestSatelliteSessionController.isEsosInActivityTimerStarted());
+
+        mTestSatelliteSessionController.setDeviceAlignedWithSatellite(true);
+
+        // Verify that the ESOS inactivity timer is stopped.
+        assertFalse(mTestSatelliteSessionController.isEsosInActivityTimerStarted());
+
+        mTestSatelliteSessionController.setDeviceAlignedWithSatellite(false);
+
+        // Verify that the ESOS inactivity timer is started.
+        assertTrue(mTestSatelliteSessionController.isEsosInActivityTimerStarted());
+
+        // Time shift to cause timeout
+        moveTimeForward(ESOS_INACTIVITY_TIMEOUT_SEC * 1000);
+        processAllMessages();
+
+        // SatelliteSessionController should move to IDLE state.
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+    }
+
+    @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);
+
+        // 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);
+
+        // 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);
+
+        // 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);
+
+        // 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);
+
+        // 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);
+
+        // 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.
@@ -205,7 +956,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.
@@ -217,7 +969,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.
@@ -229,7 +982,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.
@@ -241,7 +995,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.
@@ -254,7 +1009,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.
@@ -266,7 +1022,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.
@@ -279,7 +1036,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.
@@ -292,7 +1050,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.
@@ -304,7 +1063,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.
@@ -315,7 +1075,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.
@@ -339,7 +1100,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.
@@ -351,7 +1113,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.
@@ -362,7 +1125,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
@@ -374,7 +1138,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.
@@ -385,7 +1150,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
@@ -461,7 +1227,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.
@@ -474,8 +1241,8 @@
         processAllMessages();
 
         // SatelliteSessionController should move to CONNECTED state
-        assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
         assertTrue(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
         verify(mMockDatagramController).onSatelliteModemStateChanged(
@@ -484,7 +1251,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.
@@ -499,7 +1267,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.
@@ -514,7 +1283,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.
@@ -529,7 +1299,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.
@@ -544,7 +1315,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.
@@ -558,7 +1330,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.
@@ -573,7 +1346,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.
@@ -587,7 +1361,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.
@@ -615,7 +1390,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.
@@ -632,8 +1408,8 @@
         processAllMessages();
 
         // SatelliteSessionController should move to CONNECTED state
-        assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
         verify(mMockDatagramController).onSatelliteModemStateChanged(
                 SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
@@ -658,8 +1434,8 @@
         processAllMessages();
 
         // SatelliteSessionController should move to CONNECTED state
-        assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
         verify(mMockDatagramController).onSatelliteModemStateChanged(
                 SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
@@ -694,8 +1470,8 @@
         processAllMessages();
 
         // SatelliteSessionController should move to CONNECTED state
-        assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
 
         // Wait for timeout
@@ -713,7 +1489,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 stay at IDLE state because it failed to disable
@@ -769,7 +1546,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();
 
@@ -781,10 +1559,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());
 
@@ -908,7 +1688,7 @@
         moveSatelliteToEnablingState();
 
         // Satellite enablement has failed
-        mTestSatelliteSessionController.onSatelliteEnablementFailed();
+        mTestSatelliteSessionController.onSatelliteEnablementFailed(true);
         processAllMessages();
 
         // Satellite should move back to POWER_OFF state
@@ -936,28 +1716,41 @@
         assertNotNull(mTestSatelliteSessionController);
         assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
 
-        // IDLE -> DISABLING
+        // IDLE -> DISABLING request failed -> NOT_CONNECTED
         moveToIdleState();
-        moveSatelliteToDisablingState();
+        moveSatelliteToDisablingRequestFailed(SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED,
+                STATE_NOT_CONNECTED);
 
-        // DISABLING -> POWER_OFF
+        // NOT_CONNECTED -> DISABLING -> POWER_OFF
+        moveSatelliteToDisablingState();
         moveToPowerOffState();
 
-        // TRANSFERRING -> DISABLING
+        // IDLE -> DISABLING -> POWER_OFF
+        moveToIdleState();
+        moveSatelliteToDisablingState();
+        moveToPowerOffState();
+
+        // TRANSFERRING -> DISABLING request failed -> CONNECTED
+        moveToIdleState();
+        moveIdleToTransferringState();
+        moveSatelliteToDisablingRequestFailed(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED, STATE_CONNECTED);
+
+        // CONNECTED -> DISABLING -> POWER_OFF
+        moveSatelliteToDisablingState();
+        moveToPowerOffState();
+
+        // TRANSFERRING -> DISABLING -> POWER_OFF
         moveToIdleState();
         moveIdleToTransferringState();
         moveSatelliteToDisablingState();
-
-        // DISABLING -> POWER_OFF
         moveToPowerOffState();
 
-        // LISTENING -> DISABLING
+        // LISTENING -> DISABLING -> POWER_OFF
         moveToIdleState();
         moveIdleToTransferringState();
         moveTransferringToListeningState();
         moveSatelliteToDisablingState();
-
-        // DISABLING -> POWER_OFF
         moveToPowerOffState();
     }
 
@@ -986,6 +1779,62 @@
         moveToPowerOffState();
     }
 
+    @Test
+    public void testEmergencyModeChanged() {
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(true);
+        when(mMockSatelliteController.getRequestIsEmergency()).thenReturn(false);
+
+        // Unregister exist callback
+        mTestSatelliteSessionController.unregisterForSatelliteModemStateChanged(
+                mTestSatelliteModemStateCallback);
+
+        // Register callback
+        mTestSatelliteSessionController.registerForSatelliteModemStateChanged(
+                mTestSatelliteModemStateCallback);
+
+        // Verify initial notification
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        assertSuccessfulEmergencyModeChangedCallback(
+                mTestSatelliteModemStateCallback, false);
+
+        mTestSatelliteSessionController.onEmergencyModeChanged(true);
+
+        assertSuccessfulEmergencyModeChangedCallback(
+                mTestSatelliteModemStateCallback, true);
+
+        mTestSatelliteSessionController.onEmergencyModeChanged(false);
+
+        assertSuccessfulEmergencyModeChangedCallback(
+                mTestSatelliteModemStateCallback, false);
+
+        when(mFeatureFlags.carrierRoamingNbIotNtn()).thenReturn(false);
+        mTestSatelliteSessionController.onEmergencyModeChanged(false);
+
+        assertEmergencyModeChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+    }
+
+
+    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 isTransferring) {
         when(mMockDatagramController.isSendingInIdleState()).thenReturn(isTransferring);
         when(mMockDatagramController.isPollingInIdleState()).thenReturn(isTransferring);
@@ -1049,7 +1898,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.
@@ -1064,7 +1914,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.
@@ -1080,7 +1931,6 @@
         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);
@@ -1093,8 +1943,8 @@
         processAllMessages();
 
         // SatelliteSessionController should move to CONNECTED state
-        assertSuccessfulModemStateChangedCallback(
-                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
         verify(mMockDatagramController).onSatelliteModemStateChanged(
                 SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
@@ -1112,6 +1962,18 @@
                 STATE_DISABLING_SATELLITE, mTestSatelliteSessionController.getCurrentStateName());
     }
 
+    private void moveSatelliteToDisablingRequestFailed(int state, String stateName) {
+        moveSatelliteToDisablingState();
+
+        // Satellite disabled request failed
+        mTestSatelliteSessionController.onSatelliteEnablementFailed(false);
+        processAllMessages();
+
+        // Satellite should stay in previous state as satellite disable request failed
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback, state);
+        assertEquals(stateName, mTestSatelliteSessionController.getCurrentStateName());
+    }
+
     private static class TestSatelliteModemInterface extends SatelliteModemInterface {
         private final AtomicInteger mListeningEnabledCount = new AtomicInteger(0);
         private final AtomicInteger mListeningDisabledCount = new AtomicInteger(0);
@@ -1164,6 +2026,8 @@
     }
 
     private static class TestSatelliteSessionController extends SatelliteSessionController {
+        boolean mSatelliteEnabledForNtnOnlySubscription = true;
+
         TestSatelliteSessionController(Context context, Looper looper, FeatureFlags featureFlags,
                 boolean isSatelliteSupported,
                 SatelliteModemInterface satelliteModemInterface) {
@@ -1185,12 +2049,22 @@
         boolean isEventDeferred(int event) {
             return hasDeferredMessages(event);
         }
+
+        protected boolean isSatelliteEnabledForNtnOnlySubscription() {
+            return mSatelliteEnabledForNtnOnlySubscription;
+        }
+
+        void setSatelliteEnabledForNtnOnlySubscription(boolean enabled) {
+            mSatelliteEnabledForNtnOnlySubscription = false;
+        }
     }
 
     private static class TestSatelliteModemStateCallback extends ISatelliteModemStateCallback.Stub {
         private final AtomicInteger mModemState = new AtomicInteger(
                 SatelliteManager.SATELLITE_MODEM_STATE_OFF);
-        private final Semaphore mSemaphore = new Semaphore(0);
+        private final AtomicBoolean mIsEmergency = new AtomicBoolean(false);
+        private final Semaphore mSemaphoreForModemStateChanged = new Semaphore(0);
+        private final Semaphore mSemaphoreForEmergencyModeChanged = new Semaphore(0);
         private final Object mLock = new Object();
         private final List<Integer> mModemStates = new ArrayList<>();
 
@@ -1202,15 +2076,37 @@
                 mModemStates.add(state);
             }
             try {
-                mSemaphore.release();
+                mSemaphoreForModemStateChanged.release();
             } catch (Exception ex) {
                 logd("onSatelliteModemStateChanged: Got exception, ex=" + ex);
             }
         }
 
-        public boolean waitUntilResult() {
+        @Override
+        public void onEmergencyModeChanged(boolean isEmergency) {
+            logd("onEmergencyModeChanged: state=" + isEmergency);
+            mIsEmergency.set(isEmergency);
             try {
-                if (!mSemaphore.tryAcquire(EVENT_PROCESSING_TIME_MILLIS, TimeUnit.MILLISECONDS)) {
+                mSemaphoreForEmergencyModeChanged.release();
+            } catch (Exception ex) {
+                logd("onEmergencyModeChanged: Got exception, ex=" + ex);
+            }
+        }
+
+        @Override
+        public void onRegistrationFailure(int causeCode) {
+            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,
+                        TimeUnit.MILLISECONDS)) {
                     logd("Timeout to receive onSatelliteModemStateChanged");
                     return false;
                 }
@@ -1221,6 +2117,20 @@
             }
         }
 
+        public boolean waitUntilResultForEmergencyModeChanged() {
+            try {
+                if (!mSemaphoreForEmergencyModeChanged.tryAcquire(EVENT_PROCESSING_TIME_MILLIS,
+                        TimeUnit.MILLISECONDS)) {
+                    logd("Timeout to receive onEmergencyModeChanged");
+                    return false;
+                }
+                return true;
+            } catch (Exception ex) {
+                logd("onEmergencyModeChanged: Got exception=" + ex);
+                return false;
+            }
+        }
+
         public int getModemState() {
             return mModemState.get();
         }
@@ -1243,22 +2153,58 @@
             }
         }
 
+        public boolean getEmergencyMode() {
+            return mIsEmergency.get();
+        }
+
         public void clearSemaphorePermits() {
-            mSemaphore.drainPermits();
+            mSemaphoreForModemStateChanged.drainPermits();
         }
     }
 
     private static void assertSuccessfulModemStateChangedCallback(
             TestSatelliteModemStateCallback callback,
             @SatelliteManager.SatelliteModemState int expectedModemState) {
-        boolean successful = callback.waitUntilResult();
+        boolean successful = callback.waitUntilResultForModemStateChanged();
         assertTrue(successful);
         assertEquals(expectedModemState, callback.getModemState());
     }
 
     private static void assertModemStateChangedCallbackNotCalled(
             TestSatelliteModemStateCallback callback) {
-        boolean successful = callback.waitUntilResult();
+        boolean successful = callback.waitUntilResultForModemStateChanged();
         assertFalse(successful);
     }
+
+    private static void assertSuccessfulEmergencyModeChangedCallback(
+            TestSatelliteModemStateCallback callback,
+            boolean isEmergency) {
+        boolean successful = callback.waitUntilResultForEmergencyModeChanged();
+        assertTrue(successful);
+        assertEquals(isEmergency, callback.getEmergencyMode());
+    }
+
+    private static void assertEmergencyModeChangedCallbackNotCalled(
+            TestSatelliteModemStateCallback callback) {
+        boolean successful = callback.waitUntilResultForEmergencyModeChanged();
+        assertFalse(successful);
+    }
+
+    private void sendScreenStateChanged(Handler h, int what, boolean screenOn) {
+        Message msg = Message.obtain();
+
+        msg.what = what;
+        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 62b9def..5e560bc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
@@ -122,8 +122,8 @@
     static final int FAKE_USER_ID2 = 11;
     static final int FAKE_SATELLITE_ATTACH_FOR_CARRIER_ENABLED = 1;
     static final int FAKE_SATELLITE_ATTACH_FOR_CARRIER_DISABLED = 0;
-    static final int FAKE_SATELLITE_IS_NTN_ENABLED = 1;
-    static final int FAKE_SATELLITE_IS_NTN_DISABLED = 0;
+    static final int FAKE_SATELLITE_IS_ONLY_NTN_ENABLED = 1;
+    static final int FAKE_SATELLITE_IS_ONLY_NTN_DISABLED = 0;
     static final int FAKE_SERVICE_CAPABILITIES_1 =
             SubscriptionManager.SERVICE_CAPABILITY_DATA_BITMASK;
     static final int FAKE_SERVICE_CAPABILITIES_2 =
@@ -132,6 +132,8 @@
     static final int FAKE_SATELLITE_ENTITLEMENT_STATUS_DISABLED = 0;
     static final String FAKE_SATELLITE_ENTITLEMENT_PLMNS1 = "123123,12310";
     static final String FAKE_SATELLITE_ENTITLEMENT_PLMNS2 = "";
+    static final int FAKE_SATELLITE_ESOS_SUPPORTED_ENABLED = 1;
+    static final int FAKE_SATELLITE_ESOS_SUPPORTED_DISABLED = 0;
 
     static final String FAKE_MAC_ADDRESS1 = "DC:E5:5B:38:7D:40";
     static final String FAKE_MAC_ADDRESS2 = "DC:B5:4F:47:F3:4C";
@@ -141,6 +143,9 @@
     static final int FAKE_TRANSFER_STATUS_TRANSFERRED_OUT = 1;
     static final int FAKE_TRANSFER_STATUS_CONVERTED = 2;
 
+    static final int FAKE_SATELLITE_PROVISIONED = 1;
+    static final int FAKE_SATELLITE_NOT_PROVISIONED = 0;
+
     static final SubscriptionInfoInternal FAKE_SUBSCRIPTION_INFO1 =
             new SubscriptionInfoInternal.Builder()
                     .setId(1)
@@ -208,12 +213,14 @@
                     .setSatelliteEnabled(0)
                     .setSatelliteAttachEnabledForCarrier(
                             FAKE_SATELLITE_ATTACH_FOR_CARRIER_DISABLED)
-                    .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_NTN_DISABLED)
+                    .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_ONLY_NTN_DISABLED)
                     .setGroupDisabled(false)
                     .setServiceCapabilities(FAKE_SERVICE_CAPABILITIES_1)
                     .setTransferStatus(FAKE_TRANSFER_STATUS_TRANSFERRED_OUT)
                     .setSatelliteEntitlementStatus(FAKE_SATELLITE_ENTITLEMENT_STATUS_DISABLED)
                     .setSatelliteEntitlementPlmns(FAKE_SATELLITE_ENTITLEMENT_PLMNS2)
+                    .setSatelliteESOSSupported(FAKE_SATELLITE_ESOS_SUPPORTED_DISABLED)
+                    .setIsSatelliteProvisionedForNonIpDatagram(FAKE_SATELLITE_NOT_PROVISIONED)
                     .build();
 
     static final SubscriptionInfoInternal FAKE_SUBSCRIPTION_INFO2 =
@@ -283,12 +290,14 @@
                     .setSatelliteEnabled(1)
                     .setSatelliteAttachEnabledForCarrier(
                             FAKE_SATELLITE_ATTACH_FOR_CARRIER_ENABLED)
-                    .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_NTN_ENABLED)
+                    .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_ONLY_NTN_ENABLED)
                     .setGroupDisabled(false)
                     .setServiceCapabilities(FAKE_SERVICE_CAPABILITIES_2)
                     .setTransferStatus(FAKE_TRANSFER_STATUS_CONVERTED)
                     .setSatelliteEntitlementStatus(FAKE_SATELLITE_ENTITLEMENT_STATUS_ENABLED)
                     .setSatelliteEntitlementPlmns(FAKE_SATELLITE_ENTITLEMENT_PLMNS1)
+                    .setSatelliteESOSSupported(FAKE_SATELLITE_ESOS_SUPPORTED_ENABLED)
+                    .setIsSatelliteProvisionedForNonIpDatagram(FAKE_SATELLITE_PROVISIONED)
                     .build();
 
     private SubscriptionDatabaseManager mDatabaseManagerUT;
@@ -448,8 +457,8 @@
         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(),
                 mFeatureFlags, mSubscriptionDatabaseManagerCallback);
         logd("SubscriptionDatabaseManagerTest -Setup!");
@@ -2010,28 +2019,28 @@
         // exception is expected if there is nothing in the database.
         assertThrows(IllegalArgumentException.class,
                 () -> mDatabaseManagerUT.setNtn(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
-                        FAKE_SATELLITE_IS_NTN_ENABLED));
+                        FAKE_SATELLITE_IS_ONLY_NTN_ENABLED));
 
         SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
         mDatabaseManagerUT.setNtn(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
-                FAKE_SATELLITE_IS_NTN_ENABLED);
+                FAKE_SATELLITE_IS_ONLY_NTN_ENABLED);
         processAllMessages();
 
         subInfo = new SubscriptionInfoInternal.Builder(subInfo)
-                .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_NTN_ENABLED)
+                .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_ONLY_NTN_ENABLED)
                 .build();
         verifySubscription(subInfo);
         verify(mSubscriptionDatabaseManagerCallback, times(2)).onSubscriptionChanged(eq(1));
 
         assertThat(mDatabaseManagerUT.getSubscriptionProperty(
                 FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
-                SimInfo.COLUMN_IS_NTN)).isEqualTo(FAKE_SATELLITE_IS_NTN_ENABLED);
+                SimInfo.COLUMN_IS_ONLY_NTN)).isEqualTo(FAKE_SATELLITE_IS_ONLY_NTN_ENABLED);
 
         mDatabaseManagerUT.setSubscriptionProperty(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
-                SimInfo.COLUMN_IS_NTN, FAKE_SATELLITE_IS_NTN_DISABLED);
+                SimInfo.COLUMN_IS_ONLY_NTN, FAKE_SATELLITE_IS_ONLY_NTN_DISABLED);
         assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
                         FAKE_SUBSCRIPTION_INFO1.getSubscriptionId()).getOnlyNonTerrestrialNetwork())
-                .isEqualTo(FAKE_SATELLITE_IS_NTN_DISABLED);
+                .isEqualTo(FAKE_SATELLITE_IS_ONLY_NTN_DISABLED);
     }
 
     @Test
@@ -2074,13 +2083,13 @@
         SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
         mDatabaseManagerUT.setSatelliteAttachEnabledForCarrier(
                 FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
-                FAKE_SATELLITE_IS_NTN_DISABLED);
+                FAKE_SATELLITE_IS_ONLY_NTN_DISABLED);
         processAllMessages();
 
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
         reset(mSubscriptionDatabaseManagerCallback);
         subInfo = new SubscriptionInfoInternal.Builder(subInfo)
-                .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_NTN_ENABLED)
+                .setOnlyNonTerrestrialNetwork(FAKE_SATELLITE_IS_ONLY_NTN_ENABLED)
                 .build();
 
         int subId = subInfo.getSubscriptionId();
@@ -2100,13 +2109,13 @@
 
         assertThat(mDatabaseManagerUT.getSubscriptionProperty(
                 FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
-                SimInfo.COLUMN_IS_NTN)).isNotEqualTo(FAKE_SATELLITE_IS_NTN_ENABLED);
+                SimInfo.COLUMN_IS_ONLY_NTN)).isNotEqualTo(FAKE_SATELLITE_IS_ONLY_NTN_ENABLED);
 
         mDatabaseManagerUT.setSubscriptionProperty(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
-                SimInfo.COLUMN_IS_NTN, FAKE_SATELLITE_IS_NTN_ENABLED);
+                SimInfo.COLUMN_IS_ONLY_NTN, FAKE_SATELLITE_IS_ONLY_NTN_ENABLED);
         assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
                 FAKE_SUBSCRIPTION_INFO1.getSubscriptionId()).getOnlyNonTerrestrialNetwork())
-                .isNotEqualTo(FAKE_SATELLITE_IS_NTN_ENABLED);
+                .isNotEqualTo(FAKE_SATELLITE_IS_ONLY_NTN_ENABLED);
     }
 
     @Test
@@ -2357,4 +2366,69 @@
                 .getSatelliteEntitlementPlmns())
                 .isEqualTo(FAKE_SATELLITE_ENTITLEMENT_PLMNS2);
     }
+
+    @Test
+    public void testUpdateSatelliteESOSSupported() throws Exception {
+        // exception is expected if there is nothing in the database.
+        assertThrows(IllegalArgumentException.class,
+                () -> mDatabaseManagerUT.setSatelliteESOSSupported(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                        FAKE_SATELLITE_ESOS_SUPPORTED_ENABLED));
+
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        mDatabaseManagerUT.setSatelliteESOSSupported(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                FAKE_SATELLITE_ESOS_SUPPORTED_ENABLED);
+        processAllMessages();
+
+        subInfo = new SubscriptionInfoInternal.Builder(subInfo)
+                .setSatelliteESOSSupported(FAKE_SATELLITE_ESOS_SUPPORTED_ENABLED)
+                .build();
+        verifySubscription(subInfo);
+        verify(mSubscriptionDatabaseManagerCallback, times(2)).onSubscriptionChanged(eq(1));
+
+        assertThat(mDatabaseManagerUT.getSubscriptionProperty(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_SATELLITE_ESOS_SUPPORTED)).isEqualTo(
+                FAKE_SATELLITE_ESOS_SUPPORTED_ENABLED);
+
+        mDatabaseManagerUT.setSubscriptionProperty(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_SATELLITE_ESOS_SUPPORTED, FAKE_SATELLITE_ESOS_SUPPORTED_DISABLED);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId())
+                .getSatelliteESOSSupported()).isEqualTo(FAKE_SATELLITE_ESOS_SUPPORTED_DISABLED);
+    }
+
+    @Test
+    public void testUpdateSatelliteProvisionedStatus() throws Exception {
+        // exception is expected if there is nothing in the database.
+        assertThrows(IllegalArgumentException.class,
+                () -> mDatabaseManagerUT.setIsSatelliteProvisionedForNonIpDatagram(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                        FAKE_SATELLITE_PROVISIONED));
+
+        SubscriptionInfoInternal subInfo = insertSubscriptionAndVerify(FAKE_SUBSCRIPTION_INFO1);
+        mDatabaseManagerUT.setIsSatelliteProvisionedForNonIpDatagram(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                FAKE_SATELLITE_PROVISIONED);
+        processAllMessages();
+
+        subInfo = new SubscriptionInfoInternal.Builder(subInfo)
+                .setIsSatelliteProvisionedForNonIpDatagram(FAKE_SATELLITE_PROVISIONED)
+                .build();
+        verifySubscription(subInfo);
+        verify(mSubscriptionDatabaseManagerCallback, times(2)).onSubscriptionChanged(eq(1));
+
+        assertThat(mDatabaseManagerUT.getSubscriptionProperty(
+                FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM))
+                .isEqualTo(FAKE_SATELLITE_PROVISIONED);
+
+        mDatabaseManagerUT.setSubscriptionProperty(FAKE_SUBSCRIPTION_INFO1.getSubscriptionId(),
+                SimInfo.COLUMN_IS_SATELLITE_PROVISIONED_FOR_NON_IP_DATAGRAM,
+                FAKE_SATELLITE_NOT_PROVISIONED);
+        assertThat(mDatabaseManagerUT.getSubscriptionInfoInternal(
+                        FAKE_SUBSCRIPTION_INFO1.getSubscriptionId())
+                .getIsSatelliteProvisionedForNonIpDatagram())
+                .isEqualTo(FAKE_SATELLITE_NOT_PROVISIONED);
+    }
 }
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 de43b85..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;
 
@@ -110,7 +108,7 @@
                             SubscriptionDatabaseManagerTest
                                     .FAKE_SATELLITE_ATTACH_FOR_CARRIER_ENABLED)
                     .setOnlyNonTerrestrialNetwork(
-                            SubscriptionDatabaseManagerTest.FAKE_SATELLITE_IS_NTN_ENABLED)
+                            SubscriptionDatabaseManagerTest.FAKE_SATELLITE_IS_ONLY_NTN_ENABLED)
                     .setGroupDisabled(false)
                     .setOnlyNonTerrestrialNetwork(1)
                     .setServiceCapabilities(
@@ -122,6 +120,10 @@
                     .setSatelliteEntitlementPlmns(
                             SubscriptionDatabaseManagerTest
                                     .FAKE_SATELLITE_ENTITLEMENT_PLMNS1)
+                    .setSatelliteESOSSupported(
+                            SubscriptionDatabaseManagerTest.FAKE_SATELLITE_ESOS_SUPPORTED_ENABLED)
+                    .setIsSatelliteProvisionedForNonIpDatagram(
+                            SubscriptionDatabaseManagerTest.FAKE_SATELLITE_PROVISIONED)
                     .build();
 
     private final SubscriptionInfoInternal mSubInfoNull =
@@ -156,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);
@@ -240,7 +241,7 @@
                 .isEqualTo(SubscriptionDatabaseManagerTest
                         .FAKE_SATELLITE_ATTACH_FOR_CARRIER_ENABLED);
         assertThat(mSubInfo.getOnlyNonTerrestrialNetwork()).isEqualTo(
-                SubscriptionDatabaseManagerTest.FAKE_SATELLITE_IS_NTN_ENABLED);
+                SubscriptionDatabaseManagerTest.FAKE_SATELLITE_IS_ONLY_NTN_ENABLED);
         assertThat(mSubInfo.isGroupDisabled()).isFalse();
         assertThat(mSubInfo.getOnlyNonTerrestrialNetwork()).isEqualTo(1);
         assertThat(mSubInfo.getServiceCapabilities()).isEqualTo(
@@ -252,6 +253,10 @@
         assertThat(mSubInfo.getSatelliteEntitlementPlmns())
                 .isEqualTo(SubscriptionDatabaseManagerTest
                         .FAKE_SATELLITE_ENTITLEMENT_PLMNS1);
+        assertThat(mSubInfo.getSatelliteESOSSupported())
+                .isEqualTo(SubscriptionDatabaseManagerTest.FAKE_SATELLITE_ESOS_SUPPORTED_ENABLED);
+        assertThat(mSubInfo.getIsSatelliteProvisionedForNonIpDatagram())
+                .isEqualTo(SubscriptionDatabaseManagerTest.FAKE_SATELLITE_PROVISIONED);
     }
 
     @Test
@@ -263,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 eb06ff1..66b2eb8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
@@ -44,7 +44,7 @@
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_RCS_CONFIG1;
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_RCS_CONFIG2;
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_SATELLITE_ENTITLEMENT_PLMNS1;
-import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_SATELLITE_IS_NTN_DISABLED;
+import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_SATELLITE_IS_ONLY_NTN_DISABLED;
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_SUBSCRIPTION_INFO1;
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_SUBSCRIPTION_INFO2;
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_UUID1;
@@ -70,6 +70,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;
@@ -109,8 +110,6 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.euicc.EuiccController;
-import com.android.internal.telephony.flags.FeatureFlags;
-import com.android.internal.telephony.subscription.SubscriptionDatabaseManager.SubscriptionDatabaseManagerCallback;
 import com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.SubscriptionProvider;
 import com.android.internal.telephony.subscription.SubscriptionManagerService.BinderWrapper;
 import com.android.internal.telephony.subscription.SubscriptionManagerService.SubscriptionManagerServiceCallback;
@@ -164,7 +163,6 @@
     // mocked
     private SubscriptionManagerServiceCallback mMockedSubscriptionManagerServiceCallback;
     private EuiccController mEuiccController;
-    private FeatureFlags mFlags;
     private BinderWrapper mBinder;
     private Set<Integer> mActiveSubs = new ArraySet<>();
 
@@ -215,9 +213,13 @@
         ((MockContentResolver) mContext.getContentResolver()).addProvider(
                 Telephony.Carriers.CONTENT_URI.getAuthority(), mSubscriptionProvider);
 
-        mFlags = Mockito.mock(FeatureFlags.class);
+        doReturn(true).when(mFeatureFlags).saferGetPhoneNumber();
+        doReturn(true).when(mFeatureFlags).uiccPhoneNumberFix();
+        doReturn(true).when(mFeatureFlags).ddsCallback();
+        doReturn(true).when(mFeatureFlags).oemEnabledSatelliteFlag();
+
         mSubscriptionManagerServiceUT = new SubscriptionManagerService(mContext, Looper.myLooper(),
-                mFlags);
+                mFeatureFlags);
 
         monitorTestableLooper(new TestableLooper(getBackgroundHandler().getLooper()));
         monitorTestableLooper(new TestableLooper(getSubscriptionDatabaseManager().getLooper()));
@@ -240,8 +242,6 @@
         doReturn(true).when(mUserManager)
                 .isManagedProfile(eq(FAKE_MANAGED_PROFILE_USER_HANDLE.getIdentifier()));
 
-        // Due to affect exist implementation, bypass feature flag.
-        doReturn(false).when(mFlags).enforceTelephonyFeatureMappingForPublicApis();
         doReturn(true).when(mPackageManager).hasSystemFeature(
                 eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
         logd("SubscriptionManagerServiceTest -Setup!");
@@ -275,12 +275,6 @@
         return (SubscriptionDatabaseManager) field.get(mSubscriptionManagerServiceUT);
     }
 
-    private SubscriptionDatabaseManagerCallback getSubscriptionDatabaseCallback() throws Exception {
-        Field field = SubscriptionDatabaseManager.class.getDeclaredField("mCallback");
-        field.setAccessible(true);
-        return (SubscriptionDatabaseManagerCallback) field.get(getSubscriptionDatabaseManager());
-    }
-
     /**
      * Insert the subscription info to the database. This is an instant insertion method. For real
      * insertion sequence please use {@link #testInsertNewSim()}.
@@ -457,7 +451,7 @@
     @Test
     @DisableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
     public void testSetPhoneNumber() {
-        doReturn(false).when(mFlags).enforceTelephonyFeatureMapping();
+        doReturn(false).when(mFeatureFlags).enforceTelephonyFeatureMapping();
         doReturn(true).when(mPackageManager).hasSystemFeature(
                 eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
 
@@ -517,7 +511,7 @@
                 mSubscriptionManagerServiceUT, vendorApiLevel);
 
         // Enabled FeatureFlags and ENABLE_FEATURE_MAPPING, telephony features are defined
-        doReturn(true).when(mFlags).enforceTelephonyFeatureMappingForPublicApis();
+        doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
         doReturn(true).when(mPackageManager).hasSystemFeature(
                 eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
         try {
@@ -786,6 +780,8 @@
 
         assertThat(b.containsKey(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX)).isTrue();
         assertThat(b.getInt(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX)).isEqualTo(1);
+
+        verify(mMockedSubscriptionManagerServiceCallback).onDefaultDataSubscriptionChanged(eq(1));
     }
 
     @Test
@@ -1128,9 +1124,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();
@@ -1143,6 +1142,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(
@@ -1150,6 +1151,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()));
@@ -1281,8 +1284,8 @@
     @EnableCompatChanges({SubscriptionManagerService.REQUIRE_DEVICE_IDENTIFIERS_FOR_GROUP_UUID,
             SubscriptionManagerService.FILTER_ACCESSIBLE_SUBS_BY_USER})
     public void testIsSubscriptionAssociatedWithUserMultiSubs() {
-        doReturn(true).when(mFlags).workProfileApiSplit();
-        doReturn(true).when(mFlags).enforceSubscriptionUserFilter();
+        doReturn(true).when(mFeatureFlags).workProfileApiSplit();
+        doReturn(true).when(mFeatureFlags).enforceSubscriptionUserFilter();
         mContextFixture.addCallingOrSelfPermission(
                 Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
         insertSubscription(FAKE_SUBSCRIPTION_INFO1);
@@ -1344,8 +1347,8 @@
     public void testSubscriptionAssociationWorkProfileCallerVisibility() {
         // Split mode is defined as when a profile owns a dedicated sub, it loses the visibility to
         // the unassociated sub.
-        doReturn(true).when(mFlags).enforceSubscriptionUserFilter();
-        doReturn(true).when(mFlags).workProfileApiSplit();
+        doReturn(true).when(mFeatureFlags).enforceSubscriptionUserFilter();
+        doReturn(true).when(mFeatureFlags).workProfileApiSplit();
         mContextFixture.addCallingOrSelfPermission(
                 Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
         // Sub 1 is associated with work profile; Sub 2 is unassociated.
@@ -1368,6 +1371,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
@@ -1480,8 +1486,8 @@
     public void testSubscriptionAssociationPersonalCallerVisibility() {
         // Split mode is defined as when a profile owns a dedicated sub, it loses the visibility to
         // the unassociated sub.
-        doReturn(true).when(mFlags).enforceSubscriptionUserFilter();
-        doReturn(true).when(mFlags).workProfileApiSplit();
+        doReturn(true).when(mFeatureFlags).enforceSubscriptionUserFilter();
+        doReturn(true).when(mFeatureFlags).workProfileApiSplit();
         mContextFixture.addCallingOrSelfPermission(
                 Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION);
         // Sub 1 is unassociated; Sub 2 is associated with work profile.
@@ -1504,6 +1510,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
@@ -1832,7 +1841,7 @@
         multiNumberSubInfo =
                 new SubscriptionInfoInternal.Builder(multiNumberSubInfo)
                         .setNumberFromCarrier("")
-                        .setNumber(phoneNumberFromUicc)
+                        .setNumber("")
                         .setNumberFromIms(phoneNumberFromIms)
                         .build();
         subId = insertSubscription(multiNumberSubInfo);
@@ -2524,6 +2533,23 @@
     }
 
     @Test
+    public void testGetPhoneNumberFromUicc() {
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+        testSetPhoneNumber();
+        // Number from line1Number should be FAKE_PHONE_NUMBER1 instead of FAKE_PHONE_NUMBER2
+        assertThat(mSubscriptionManagerServiceUT.getPhoneNumber(1,
+                SubscriptionManager.PHONE_NUMBER_SOURCE_UICC, CALLING_PACKAGE, CALLING_FEATURE))
+                .isEqualTo(FAKE_PHONE_NUMBER1);
+
+        doReturn("").when(mPhone).getLine1Number();
+
+        // If getLine1Number is empty, then the number should be from the sub info.
+        assertThat(mSubscriptionManagerServiceUT.getPhoneNumber(1,
+                SubscriptionManager.PHONE_NUMBER_SOURCE_UICC, CALLING_PACKAGE, CALLING_FEATURE))
+                .isEqualTo(FAKE_PHONE_NUMBER2);
+    }
+
+    @Test
     public void testGetPhoneNumberFromInactiveSubscription() {
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
         testInactiveSimRemoval();
@@ -2543,8 +2569,6 @@
 
     @Test
     public void testGetPhoneNumberFromDefaultSubscription() {
-        doReturn(true).when(mFlags).saferGetPhoneNumber();
-
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
         int subId = insertSubscription(FAKE_SUBSCRIPTION_INFO1);
@@ -3192,7 +3216,6 @@
         mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
                 FAKE_CARRIER_NAME1);
         System.setProperty("persist.radio.allow_mock_modem", "true");
-        doReturn(true).when(mFlags).oemEnabledSatelliteFlag();
 
         EuiccProfileInfo profileInfo1 = new EuiccProfileInfo.Builder(FAKE_ICCID1)
                 .setIccid(FAKE_ICCID1)
@@ -3222,14 +3245,12 @@
         mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
                 FAKE_CARRIER_NAME1);
         System.setProperty("persist.radio.allow_mock_modem", "false");
-        doReturn(false).when(mFlags).oemEnabledSatelliteFlag();
     }
 
     @Test
     public void testIsSatelliteSpnWithEmptySpn() {
         mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier, ""); // Empty
         System.setProperty("persist.radio.allow_mock_modem", "true");
-        doReturn(true).when(mFlags).oemEnabledSatelliteFlag();
 
         EuiccProfileInfo profileInfo1 = new EuiccProfileInfo.Builder(FAKE_ICCID1)
                 .setIccid(FAKE_ICCID1)
@@ -3255,7 +3276,7 @@
         SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT
                 .getSubscriptionInfoInternal(1);
         assertThat(subInfo.getOnlyNonTerrestrialNetwork())
-                .isEqualTo(FAKE_SATELLITE_IS_NTN_DISABLED);
+                .isEqualTo(FAKE_SATELLITE_IS_ONLY_NTN_DISABLED);
 
         mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
                 FAKE_CARRIER_NAME1);
@@ -3282,10 +3303,9 @@
         subInfo = mSubscriptionManagerServiceUT
                 .getSubscriptionInfoInternal(2);
         assertThat(subInfo.getOnlyNonTerrestrialNetwork())
-                .isEqualTo(FAKE_SATELLITE_IS_NTN_DISABLED);
+                .isEqualTo(FAKE_SATELLITE_IS_ONLY_NTN_DISABLED);
 
         System.setProperty("persist.radio.allow_mock_modem", "false");
-        doReturn(false).when(mFlags).oemEnabledSatelliteFlag();
     }
 
     @Test
@@ -3293,7 +3313,6 @@
         mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
                 FAKE_CARRIER_NAME1);
         System.setProperty("persist.radio.allow_mock_modem", "true");
-        doReturn(true).when(mFlags).oemEnabledSatelliteFlag();
 
         EuiccProfileInfo profileInfo1 = new EuiccProfileInfo.Builder(FAKE_ICCID1)
                 .setIccid(FAKE_ICCID1)
@@ -3322,7 +3341,6 @@
         mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
                 FAKE_CARRIER_NAME1);
         System.setProperty("persist.radio.allow_mock_modem", "false");
-        doReturn(false).when(mFlags).oemEnabledSatelliteFlag();
     }
 
     @Test
@@ -3330,7 +3348,6 @@
         mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
                 FAKE_CARRIER_NAME1);
         System.setProperty("persist.radio.allow_mock_modem", "true");
-        doReturn(true).when(mFlags).oemEnabledSatelliteFlag();
 
         EuiccProfileInfo profileInfo1 = new EuiccProfileInfo.Builder(FAKE_ICCID1)
                 .setIccid(FAKE_ICCID1)
@@ -3360,7 +3377,6 @@
         mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
                 FAKE_CARRIER_NAME1);
         System.setProperty("persist.radio.allow_mock_modem", "false");
-        doReturn(false).when(mFlags).oemEnabledSatelliteFlag();
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/IsimUiccRecordsTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/IsimUiccRecordsTest.java
index 6f4666c..f33b002 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/IsimUiccRecordsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/IsimUiccRecordsTest.java
@@ -66,7 +66,7 @@
     private class IsimUiccRecordsUT extends IsimUiccRecords {
         IsimUiccRecordsUT(UiccCardApplication app, Context c,
                 CommandsInterface ci, IccFileHandler mFhMock) {
-            super(app, c, ci);
+            super(app, c, ci, mFeatureFlags);
             mFh = mFhMock;
         }
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardApplicationTest.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardApplicationTest.java
index d2490ef..10c51c3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardApplicationTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccCardApplicationTest.java
@@ -74,7 +74,7 @@
         mUiccCardAppStatus.pin2 = IccCardStatus.PinState.PINSTATE_ENABLED_VERIFIED;
 
         mUiccCardApplication = new UiccCardApplication(mUiccProfile, mUiccCardAppStatus,
-            mContext, mSimulatedCommands);
+            mContext, mSimulatedCommands, mFeatureFlags);
         mHandler = new Handler() {
             @Override
             public void handleMessage(Message msg) {
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 9265a62..58a8153 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccControllerTest.java
@@ -123,7 +123,7 @@
         // (before 1.2) of hal
         mIccCardStatus.mSlotPortMapping = new IccSlotPortMapping();
         mIccCardStatus.mSlotPortMapping.mPhysicalSlotIndex = 0;
-        mUiccControllerUT = UiccController.make(mContext);
+        mUiccControllerUT = UiccController.make(mContext, mFeatureFlags);
         // reset sLastSlotStatus so that onGetSlotStatusDone always sees a change in the slot status
         mUiccControllerUT.sLastSlotStatus = null;
         processAllMessages();
@@ -145,7 +145,7 @@
                 com.android.internal.R.array.non_removable_euicc_slots,
                 nonRemovableEuiccSlots);
         replaceInstance(UiccController.class, "mInstance", null, null);
-        mUiccControllerUT = UiccController.make(mContext);
+        mUiccControllerUT = UiccController.make(mContext, mFeatureFlags);
         processAllMessages();
     }
 
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 5c1993f..a2b42af 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java
@@ -29,7 +29,6 @@
 import static org.mockito.Mockito.verify;
 
 import android.os.Binder;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -38,11 +37,9 @@
 
 import com.android.internal.telephony.IccLogicalChannelRequest;
 import com.android.internal.telephony.TelephonyTest;
-import com.android.internal.telephony.flags.Flags;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -61,13 +58,9 @@
 
     private int mPhoneId = 0;
 
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
-        mSetFlagsRule.enableFlags(Flags.FLAG_CLEANUP_OPEN_LOGICAL_CHANNEL_RECORD_ON_DISPOSE);
         mUiccCard = mock(UiccCard.class);
         mIccCardStatus = mock(IccCardStatus.class);
         /* initially there are no application available */
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 8209dfa..f88bc1e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccStateChangedLauncherTest.java
@@ -32,6 +32,7 @@
 import android.content.res.Resources;
 import android.os.Looper;
 import android.os.Message;
+import android.os.UserHandle;
 import android.telephony.TelephonyManager;
 
 import androidx.test.filters.SmallTest;
@@ -89,7 +90,7 @@
         }
 
         UiccStateChangedLauncher uiccLauncher =
-                new UiccStateChangedLauncher(mContext, UiccController.getInstance());
+                new UiccStateChangedLauncher(mContext, UiccController.getInstance(), mFeatureFlags);
         ArgumentCaptor<Integer> integerArgumentCaptor = ArgumentCaptor.forClass(Integer.class);
         verify(UiccController.getInstance(), times(1)).registerForIccChanged(eq(uiccLauncher),
                 integerArgumentCaptor.capture(),
@@ -108,7 +109,8 @@
 
         // Amount of sent broadcasts to the device provisioning package.
         int broadcast_count = 1;
-        verify(mContext, times(broadcast_count)).sendBroadcast(intentArgumentCaptor.capture());
+        verify(mContext, times(broadcast_count)).sendBroadcastAsUser(intentArgumentCaptor.capture(),
+                eq(UserHandle.ALL));
         assertEquals(PROVISIONING_PACKAGE_NAME, intentArgumentCaptor.getValue().getPackage());
         assertEquals(TelephonyIntents.ACTION_SIM_STATE_CHANGED,
                 intentArgumentCaptor.getValue().getAction());
@@ -119,14 +121,16 @@
         uiccLauncher.handleMessage(msg);
 
         broadcast_count++;
-        verify(mContext, times(broadcast_count)).sendBroadcast(intentArgumentCaptor.capture());
+        verify(mContext, times(broadcast_count)).sendBroadcastAsUser(intentArgumentCaptor.capture(),
+                eq(UserHandle.ALL));
         assertEquals(PROVISIONING_PACKAGE_NAME, intentArgumentCaptor.getValue().getPackage());
         assertEquals(TelephonyIntents.ACTION_SIM_STATE_CHANGED,
                 intentArgumentCaptor.getValue().getAction());
 
         // Nothing's changed. Broadcast should not be sent.
         uiccLauncher.handleMessage(msg);
-        verify(mContext, times(broadcast_count)).sendBroadcast(any(Intent.class));
+        verify(mContext, times(broadcast_count)).sendBroadcastAsUser(any(Intent.class),
+                eq(UserHandle.ALL));
 
         // Card state's changed from restricted. Broadcast should be sent.
         card.update(mContext, mSimulatedCommands,
@@ -134,7 +138,8 @@
         uiccLauncher.handleMessage(msg);
 
         broadcast_count++;
-        verify(mContext, times(broadcast_count)).sendBroadcast(intentArgumentCaptor.capture());
+        verify(mContext, times(broadcast_count)).sendBroadcastAsUser(any(Intent.class),
+                eq(UserHandle.ALL));
         assertEquals(PROVISIONING_PACKAGE_NAME, intentArgumentCaptor.getValue().getPackage());
         assertEquals(TelephonyIntents.ACTION_SIM_STATE_CHANGED,
                 intentArgumentCaptor.getValue().getAction());
@@ -151,7 +156,7 @@
         }
 
         UiccStateChangedLauncher uiccLauncher =
-                new UiccStateChangedLauncher(mContext, UiccController.getInstance());
+                new UiccStateChangedLauncher(mContext, UiccController.getInstance(), mFeatureFlags);
         verify(UiccController.getInstance(), never()).registerForIccChanged(eq(uiccLauncher),
                 anyInt(), anyObject());
     }
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 c38be60..bcb5c4c 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
@@ -197,7 +197,7 @@
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi,
                 "E00582030200009000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, responses);
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
         return channel;
     }
 }
\ No newline at end of file
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 d140ca8..2fef021 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
@@ -1202,7 +1202,7 @@
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi,
                 "E00582030200009000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, responses);
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
         return channel;
     }
 
@@ -1210,7 +1210,7 @@
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi,
                 "E00582030201009000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, responses);
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
         return channel;
     }
 }
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 cf3f900..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,37 +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 androidx.test.InstrumentationRegistry;
 
 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;
@@ -76,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;
 
@@ -83,20 +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(InstrumentationRegistry.getContext(), 0 /* phoneId= */,
-                            mMockCi, AID, false /* supportExtendedApdu */);
-        mLooper = TestableLooper.get(this);
+        mSender = new ApduSender(InstrumentationRegistry.getContext(), PHONE_ID,
+                            mMockCi, ApduSender.ISD_R_AID, false /* supportExtendedApdu */);
     }
 
     @After
@@ -107,12 +129,25 @@
         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
     public void testSendEmptyCommands() throws InterruptedException {
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "A1A1A19000");
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
 
         mSender.send((selectResponse, requestBuilder) -> mSelectResponse = selectResponse,
                 mResponseCaptor, mHandler);
@@ -121,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());
     }
 
@@ -137,22 +172,25 @@
         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
     public void testSend() throws InterruptedException {
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A1A1A19000");
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
 
         mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
                 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
         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
@@ -160,7 +198,7 @@
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A19000", "A29000",
                 "A39000", "A49000");
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
 
         mSender.send((selectResponse, requestBuilder) -> {
             requestBuilder.addApdu(10, 1, 2, 3, 0, "a");
@@ -171,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
@@ -186,7 +227,7 @@
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A19000", "A29000",
                 "A39000", "A49000");
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
         mResponseCaptor.stopApduIndex = 2;
 
         mSender.send((selectResponse, requestBuilder) -> {
@@ -211,7 +252,7 @@
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A1A1A16104",
                 "B2B2B2B26102", "C3C39000");
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
 
         mSender.send((selectResponse, requestBuilder) -> requestBuilder.addApdu(
                 10, 1, 2, 3, 0, "a"), mResponseCaptor, mHandler);
@@ -231,7 +272,7 @@
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A19000", "9000", "9000",
                 "B22B6103", "B2222B9000", "C39000");
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
 
         // Each segment has 0xFF (the limit of a single command) bytes.
         String s1 = new String(new char[0xFF]).replace("\0", "AA");
@@ -262,7 +303,7 @@
     public void testSendStoreDataLongDataMod0() throws InterruptedException {
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "9000", "B2222B9000");
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
 
         // Each segment has 0xFF (the limit of a single command) bytes.
         String s1 = new String(new char[0xFF]).replace("\0", "AA");
@@ -284,7 +325,7 @@
     public void testSendStoreDataLen0() throws InterruptedException {
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "B2222B9000");
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
 
         mSender.send((selectResponse, requestBuilder) -> {
             requestBuilder.addStoreData("");
@@ -301,7 +342,7 @@
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
         LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, "A19000", "9000",
                 "B22B6103", "6985");
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
 
         // Each segment has 0xFF (the limit of a single command) bytes.
         String s1 = new String(new char[0xFF]).replace("\0", "AA");
@@ -328,7 +369,7 @@
     @Test
     public void testChannelAlreadyOpened() throws InterruptedException {
         int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi, "9000");
-        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
+        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel, /* error= */ null);
 
         ResponseCaptor outerResponseCaptor = new ResponseCaptor();
         mSender.send(
@@ -341,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();
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java
index 27f743f..e8aeed1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/euicc/apdu/LogicalChannelMocker.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.telephony.uicc.euicc.apdu;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -30,7 +31,6 @@
 import com.android.internal.telephony.uicc.IccIoResult;
 import com.android.internal.telephony.uicc.IccUtils;
 
-import org.mockito.ArgumentCaptor;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
@@ -48,13 +48,12 @@
         int[] responseInts = isException ? null : getSelectResponse(responseObject.toString());
         Throwable exception = isException ? (Throwable) responseObject : null;
 
-        ArgumentCaptor<Message> response = ArgumentCaptor.forClass(Message.class);
         doAnswer((Answer<Void>) invocation -> {
-            Message msg = response.getValue();
+            Message msg = invocation.getArgument(2);
             AsyncResult.forMessage(msg, responseInts, exception);
             msg.sendToTarget();
             return null;
-        }).when(mockCi).iccOpenLogicalChannel(anyString(), anyInt(), response.capture());
+        }).when(mockCi).iccOpenLogicalChannel(anyString(), anyInt(), any());
         return LOGICAL_CHANNEL;
     }
 
@@ -64,22 +63,20 @@
      */
     public static void mockSendToLogicalChannel(CommandsInterface mockCi, int channel,
             Object... responseObjects) {
-        ArgumentCaptor<Message> response = ArgumentCaptor.forClass(Message.class);
-
         doAnswer(new Answer() {
             private int mIndex = 0;
 
             @Override
             public Object answer(InvocationOnMock invocation) throws Throwable {
-                Object responseObject = responseObjects[mIndex++];
-                mockIccTransmitApduLogicalChannelResponse(response, responseObject);
+                Object response = responseObjects[mIndex++];
+                mockIccTransmitApduLogicalChannelResponse(invocation.getArgument(8), response);
                 return null;
             }
         }).when(mockCi).iccTransmitApduLogicalChannel(eq(channel), anyInt(), anyInt(), anyInt(),
-                anyInt(), anyInt(), anyString(), anyBoolean(), response.capture());
+                anyInt(), anyInt(), anyString(), anyBoolean(), any());
     }
 
-    private static void mockIccTransmitApduLogicalChannelResponse(ArgumentCaptor<Message> response,
+    private static void mockIccTransmitApduLogicalChannelResponse(Message msg,
             Object responseObject) throws Throwable {
 
         boolean isException = responseObject instanceof Throwable;
@@ -95,20 +92,22 @@
         IccIoResult result = isException ? null : new IccIoResult(sw1, sw2, hex);
         Throwable exception = isException ? (Throwable) responseObject : null;
 
-        Message msg = response.getValue();
         AsyncResult.forMessage(msg, result, exception);
         msg.sendToTarget();
     }
 
-    public static void mockCloseLogicalChannel(CommandsInterface mockCi, int channel) {
-        ArgumentCaptor<Message> response = ArgumentCaptor.forClass(Message.class);
+    /**
+     * @param error can be {@code null} for a success response or an exception for a failure
+     */
+    public static void mockCloseLogicalChannel(
+            CommandsInterface mockCi, int channel, @Nullable Throwable error) {
         doAnswer((Answer<Void>) invocation -> {
-            Message msg = response.getValue();
-            AsyncResult.forMessage(msg);
+            Message msg = invocation.getArgument(2);
+            AsyncResult.forMessage(msg, null, error);
             msg.sendToTarget();
             return null;
         }).when(mockCi).iccCloseLogicalChannel(eq(channel),
-                eq(true /*isEs10*/), response.capture());
+                eq(true /*isEs10*/), any());
     }
 
     private static int[] getSelectResponse(String responseHex) {