[automerger skipped] Merge "Merge Android 14 QPR3 to AOSP main" into main am: 319dd9f607 -s ours am: 4a25608c64 -s ours

am skip reason: Merged-In I684660ce44e8a71706c915543c69c21481829699 with SHA-1 d8ea43539e is already in history

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

Change-Id: I28a42cec90ca645a22ea19a9864043f45fb46a76
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 4646649..1885032 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -33,7 +33,6 @@
         "subscription.aconfig",
         "uicc.aconfig",
         "satellite.aconfig",
-        "iwlan.aconfig",
-        "telephony.aconfig",
+        "iwlan.aconfig"
     ],
 }
diff --git a/flags/calling.aconfig b/flags/calling.aconfig
index c18fa1a..c1dc7e7 100644
--- a/flags/calling.aconfig
+++ b/flags/calling.aconfig
@@ -1,16 +1,31 @@
 package: "com.android.internal.telephony.flags"
 container: "system"
 
+# 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"
+  is_exported: true
 }
 
+# OWNER=yomna TARGET=24Q3
 flag {
   name: "show_call_fail_notification_for_2g_toggle"
   namespace: "telephony"
   description: "Used in DisconnectCause and TelephonyConnection if a non-emergency call fails on a device with no 2G, to guard whether a user can see an updated error message reminding the 2G is disabled and potentially disrupting their call connectivity"
   bug: "300142897"
-}
\ No newline at end of file
+}
+
+# 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."
+    bug:"284416645"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/flags/data.aconfig b/flags/data.aconfig
index 87fbede..d956104 100644
--- a/flags/data.aconfig
+++ b/flags/data.aconfig
@@ -1,6 +1,18 @@
 package: "com.android.internal.telephony.flags"
 container: "system"
 
+# OWNER=linggm TARGET=24Q4
+flag {
+  name: "keep_empty_requests_network"
+  namespace: "telephony"
+  description: "Don't tear down network even if no requests attached to it."
+  bug: "331301784"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+# OWNER=linggm TARGET=24Q3
 flag {
   name: "auto_data_switch_allow_roaming"
   namespace: "telephony"
@@ -11,13 +23,18 @@
   }
 }
 
+# OWNER=linggm TARGET=24Q3
 flag {
-  name: "auto_data_switch_rat_ss"
+  name: "auto_data_switch_uses_data_enabled"
   namespace: "telephony"
-  description: "Whether switch for better rat and signal strength"
-  bug:"260928808"
+  description: "Separately consider the backup phone's data allowed and data enabled."
+  bug: "338552223"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
 
+# OWNER=linggm TARGET=24Q2
 flag {
   name: "use_alarm_callback"
   namespace: "telephony"
@@ -25,6 +42,7 @@
   bug: "311476875"
 }
 
+# OWNER=linggm TARGET=24Q2
 flag {
   name: "refine_preferred_data_profile_selection"
   namespace: "telephony"
@@ -32,6 +50,7 @@
   bug: "311476883"
 }
 
+# OWNER=linggm TARGET=24Q2
 flag {
   name: "unthrottle_check_transport"
   namespace: "telephony"
@@ -39,6 +58,7 @@
   bug: "303922311"
 }
 
+# OWNER=linggm TARGET=24Q1
 flag {
   name: "relax_ho_teardown"
   namespace: "telephony"
@@ -46,6 +66,7 @@
   bug: "270895912"
 }
 
+# OWNER=linggm TARGET=24Q2
 flag {
   name: "allow_mmtel_in_non_vops"
   namespace: "telephony"
@@ -53,6 +74,7 @@
   bug: "241198464"
 }
 
+# OWNER=jackyu TARGET=24Q2
 flag {
   name: "metered_embb_urlcc"
   namespace: "telephony"
@@ -60,27 +82,34 @@
   bug: "301310451"
   }
 
+# OWNER=sarahchin TARGET=24Q3
 flag {
   name: "slicing_additional_error_codes"
+  is_exported: true
   namespace: "telephony"
   description: "Support additional slicing error codes and functionality."
   bug: "307378699"
 }
 
+# OWNER=nagendranb TARGET=24Q3
 flag {
   name: "apn_setting_field_support_flag"
+  is_exported: true
   namespace: "telephony"
   description: "Expose apn setting supporting field"
   bug: "307038091"
 }
 
+# OWNER=sangyun TARGET=24Q3
 flag {
   name: "network_validation"
+  is_exported: true
   namespace: "telephony"
   description: "Request network validation for data networks and response status."
   bug:"286171724"
 }
 
+# OWNER=nagendranb TARGET=24Q2
 flag {
  name: "notify_data_activity_changed_with_slot"
   namespace: "telephony"
@@ -88,6 +117,7 @@
   bug: "309896936"
 }
 
+# OWNER=qingqi TARGET=24Q3
 flag {
   name: "vonr_enabled_metric"
   namespace: "telephony"
@@ -95,6 +125,7 @@
   bug:"288449751"
 }
 
+# OWNER=willycwhu TARGET=24Q2
 flag {
   name: "ignore_existing_networks_for_internet_allowed_checking"
   namespace: "telephony"
@@ -102,6 +133,7 @@
   bug: "284420611"
 }
 
+# OWNER=apsankar TARGET=24Q3
 flag {
   name: "data_call_session_stats_captures_cross_sim_calling"
   namespace: "telephony"
@@ -109,6 +141,7 @@
   bug: "313956117"
 }
 
+# OWNER=jackyu TARGET=24Q2
 flag {
   name: "force_iwlan_mms"
   namespace: "telephony"
@@ -116,6 +149,7 @@
   bug: "316211526"
 }
 
+# OWNER=sewook TARGET=24Q3
 flag {
   name: "reconnect_qualified_network"
   namespace: "telephony"
@@ -123,9 +157,18 @@
   bug: "319520561"
 }
 
+# OWNER=jackyu TARGET=24Q3
 flag {
   name: "dsrs_diagnostics_enabled"
   namespace: "telephony"
   description: "Enable DSRS diagnostics."
   bug: "319601607"
-}
\ No newline at end of file
+}
+
+# OWNER=jackyu TARGET=24Q3
+flag {
+  name: "data_rat_metric_enabled"
+  namespace: "telephony"
+  description: "Write DataRatStateChanged atom"
+  bug:"318519337"
+}
diff --git a/flags/domainselection.aconfig b/flags/domainselection.aconfig
index 8ca6bd3..623c3b6 100644
--- a/flags/domainselection.aconfig
+++ b/flags/domainselection.aconfig
@@ -1,6 +1,7 @@
 package: "com.android.internal.telephony.flags"
 container: "system"
 
+# OWNER=forestchoi TARGET=24Q3
 flag {
     name: "ap_domain_selection_enabled"
     namespace: "telephony"
@@ -8,6 +9,7 @@
     bug:"258112541"
 }
 
+# OWNER=forestchoi TARGET=24Q3
 flag {
     name: "use_aosp_domain_selection_service"
     namespace: "telephony"
@@ -15,13 +17,16 @@
     bug:"258112541"
 }
 
+# OWNER=forestchoi TARGET=24Q3
 flag {
     name: "use_oem_domain_selection_service"
+    is_exported: true
     namespace: "telephony"
     description: "This flag controls OEMs' domain selection service supported."
     bug:"258112541"
 }
 
+# OWNER=forestchoi TARGET=24Q3
 flag {
     name: "domain_selection_metrics_enabled"
     namespace: "telephony"
diff --git a/flags/ims.aconfig b/flags/ims.aconfig
index ca03e51..4eff505 100644
--- a/flags/ims.aconfig
+++ b/flags/ims.aconfig
@@ -1,6 +1,7 @@
 package: "com.android.internal.telephony.flags"
 container: "system"
 
+# OWNER=hyosunkim TARGET=24Q2
 flag {
     name: "conference_hold_unhold_changed_to_send_message"
     namespace: "telephony"
@@ -8,6 +9,7 @@
     bug:"288002989"
 }
 
+# OWNER=joonhunshin TARGET=24Q2
 flag {
     name: "ignore_already_terminated_incoming_call_before_registering_listener"
     namespace: "telephony"
@@ -15,6 +17,7 @@
     bug:"289461637"
 }
 
+# OWNER=joonhunshin TARGET=24Q2
 flag {
     name: "clear_cached_ims_phone_number_when_device_lost_ims_registration"
     namespace: "telephony"
@@ -22,6 +25,7 @@
     bug:"288002989"
 }
 
+# OWNER=sangyun TARGET=24Q2
 flag {
     name: "update_ims_service_by_gathering_provisioning_changes"
     namespace: "telephony"
@@ -29,13 +33,16 @@
     bug:"302281114"
 }
 
+# OWNER=shmun TARGET=24Q3
 flag {
     name: "add_rat_related_suggested_action_to_ims_registration"
+    is_exported: true
     namespace: "telephony"
     description: "This flag is for adding suggested actions related to RAT to ims registration"
     bug:"290573256"
 }
 
+# OWNER=joonhunshin TARGET=24Q3
 flag {
     name: "terminate_active_video_call_when_accepting_second_video_call_as_audio_only"
     namespace: "telephony"
@@ -43,13 +50,16 @@
     bug:"309548300"
 }
 
+# OWNER=sewookseo TARGET=24Q3
 flag {
     name: "emergency_registration_state"
+    is_exported: true
     namespace: "telephony"
     description: "This flag is created to notify emergency registration state changed."
     bug:"312101946"
 }
 
+# OWNER=apsankar TARGET=24Q3
 flag {
     name: "call_extra_for_non_hold_supported_carriers"
     namespace: "telephony"
@@ -57,9 +67,51 @@
     bug:"315993953"
 }
 
+# OWNER=joonhunshin TARGET=24Q3
 flag {
     name: "update_roaming_state_to_set_wfc_mode"
     namespace: "telephony"
     description: "This flag updates roaming state to set wfc mode"
     bug:"317298331"
 }
+
+# OWNER=joonhunshin TARGET=24Q3
+flag {
+    name: "enable_sip_subscribe_retry"
+    namespace: "telephony"
+    description: "This flag controls whether framework supports SIP subscribe retry or not"
+    bug:"297023230"
+}
+
+# OWNER=joonhunshin TARGET=24Q3
+flag {
+    name: "answer_audio_only_when_answering_via_mmi_code"
+    namespace: "telephony"
+    description: "This flag changes the media type when answering incoming call via MMI code"
+    bug:"286499659"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=joonhunshin TARGET=24Q3
+flag {
+    name: "notify_initial_ims_provisioning_status"
+    namespace: "telephony"
+    description: "This flag allows to notify initial IMS provisioning status when IFeatureProvisioningCallback registered or ImsService connected"
+    bug:"330082572"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=joonhunshin TARGET=24Q3
+flag {
+    name: "set_number_of_sim_for_ims_enable"
+    namespace: "telephony"
+    description: "This flag allows to set number of SIM for IMS enable/disable for each slot when the eSIM is added while the binding with ImsService exists"
+    bug:"331971397"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/flags/iwlan.aconfig b/flags/iwlan.aconfig
index 472baba..f16ee38 100644
--- a/flags/iwlan.aconfig
+++ b/flags/iwlan.aconfig
@@ -1,14 +1,19 @@
 package: "com.android.internal.telephony.flags"
 container: "system"
 
+# OWNER=jmunikrishna TARGET=24Q3
 flag {
     name: "enable_aead_algorithms"
+    is_exported: true
     namespace: "telephony"
     description: "Add AEAD algorithms AES-GCM-8, AES-GCM-12 and AES-GCM-16 to IWLAN"
     bug:"306119890"
 }
+
+# OWNER=jmunikrishna TARGET=24Q3
 flag {
     name: "enable_multiple_sa_proposals"
+    is_exported: true
     namespace: "telephony"
     description: "Add multiple proposals of cipher suites in IKE SA and Child SA"
     bug:"287296642"
diff --git a/flags/messaging.aconfig b/flags/messaging.aconfig
index 52c6213..1030ba7 100644
--- a/flags/messaging.aconfig
+++ b/flags/messaging.aconfig
@@ -1,13 +1,7 @@
 package: "com.android.internal.telephony.flags"
 container: "system"
 
-flag {
-  name: "reject_bad_sub_id_interaction"
-  namespace: "telephony"
-  description: "Previously, the DB allows insertion of a random sub Id, but doesn't allow query it. This change rejects such interaction."
-  bug: "294125411"
-}
-
+# OWNER=hwangoo TARGET=24Q2
 flag {
   name: "sms_domain_selection_enabled"
   namespace: "telephony"
@@ -15,9 +9,33 @@
   bug: "262804071"
 }
 
+# OWNER=tnd TARGET=24Q3
 flag {
   name: "mms_disabled_error"
+  is_exported: true
   namespace: "telephony"
   description: "This flag controls the support of the new MMS error code MMS_ERROR_MMS_DISABLED."
   bug: "305062594"
 }
+
+# OWNER=linggm TARGET=24Q4
+flag {
+  name: "mms_get_apn_from_pdsc"
+  namespace: "telephony"
+  description: "This flag controls get APN details from PDSC instead of telephony provider."
+  bug: "324280016"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+# OWNER=stevestatia TARGET=24Q3
+flag {
+    name: "unregister_sms_broadcast_receiver_from_cat_service"
+    namespace: "telephony"
+    description: "This flag will unregister the sms broadcast receiver in the CatService when the process is disposed."
+    bug: "338936403"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
\ No newline at end of file
diff --git a/flags/misc.aconfig b/flags/misc.aconfig
index a11ed3d..9d5cbd6 100644
--- a/flags/misc.aconfig
+++ b/flags/misc.aconfig
@@ -1,6 +1,18 @@
 package: "com.android.internal.telephony.flags"
 container: "system"
 
+# OWNER=linggm TARGET=24Q3
+flag {
+    name: "combine_ril_death_handle"
+    namespace: "telephony"
+    description: "Upon radio service death, combine its handling to prevent race condition"
+    bug:"319612362"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=tjstuart TARGET=24Q3
 flag {
   name: "do_not_override_precise_label"
   namespace: "telephony"
@@ -9,6 +21,7 @@
   is_fixed_read_only: true
 }
 
+# OWNER=tnd TARGET=24Q1
 flag {
   name: "log_mms_sms_database_access_info"
   namespace: "telephony"
@@ -16,6 +29,7 @@
   bug: "275225402"
 }
 
+# OWNER=tjstuart TARGET=24Q3
 flag {
   name: "stop_spamming_emergency_notification"
   namespace: "telephony"
@@ -23,13 +37,16 @@
   bug: "275225402"
 }
 
+# OWNER=avinashmp TARGET=24Q3
 flag {
   name: "enable_wps_check_api_flag"
+  is_exported: true
   namespace: "telephony"
   description: "Enable system api isWpsCallNumber. Its an utility api to check if the dialed number is for Wireless Priority Service call."
   bug: "304272356"
 }
 
+# OWNER=grantmenke TARGET=24Q3
 flag {
   name: "ensure_access_to_call_settings_is_restricted"
   namespace: "telephony"
@@ -37,6 +54,7 @@
   bug: "309655251"
 }
 
+# OWNER=sangyun TARGET=24Q2
 flag {
   name: "reorganize_roaming_notification"
   namespace: "telephony"
@@ -44,6 +62,7 @@
   bug: "310594087"
 }
 
+# OWNER=sangyun TARGET=24Q2
 flag {
   name: "dismiss_network_selection_notification_on_sim_disable"
   namespace: "telephony"
@@ -51,6 +70,7 @@
   bug: "310594186"
 }
 
+# OWNER=nagendranb TARGET=24Q3
 flag {
   name: "enable_telephony_analytics"
   namespace: "telephony"
@@ -58,20 +78,25 @@
   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: "fix_crash_on_getting_config_when_phone_is_gone"
     namespace: "telephony"
@@ -82,6 +107,7 @@
     }
 }
 
+# OWNER=rambowang TARGET=24Q3
 flag {
     name: "add_anomaly_when_notify_config_changed_with_invalid_phone"
     namespace: "telephony"
@@ -91,3 +117,103 @@
         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"
+    namespace: "telephony"
+    description: "Fix bug where roaming notification was not shown on a single data network."
+    bug:"249908996"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=joonhunshin TARGET=24Q3
+flag {
+    name: "enforce_telephony_feature_mapping"
+    namespace: "telephony"
+    description: "This flag controls telephony feature flags mapping."
+    bug:"297989574"
+}
+
+# OWNER=joonhunshin TARGET=24Q3
+flag {
+    name: "enforce_telephony_feature_mapping_for_public_apis"
+    namespace: "telephony"
+    description: "This flag controls telephony feature flags mapping for public APIs and CTS."
+    bug:"297989574"
+}
+
+# OWNER=stevestatia TARGET=24Q3
+flag {
+    name: "prevent_system_server_and_phone_deadlock"
+    namespace: "telephony"
+    description: "This flag controls the order of the binder to prevent deadlock in system_server"
+    bug: "315973270"
+}
+
+# OWNER=joonhunshin TARGET=24Q3
+flag {
+    name: "prevent_invocation_repeat_of_ril_call_when_device_does_not_support_voice"
+    namespace: "telephony"
+    description: "This flag prevents repeat invocation of call related APIs in RIL when the device is not voice capable"
+    bug: "290833783"
+}
+
+# OWNER=jackyu TARGET=24Q3
+flag {
+    name: "minimal_telephony_cdm_check"
+    namespace: "telephony"
+    description: "This flag disables Calling/Data/Messaging features if their respective feature is missing"
+    bug: "310710841"
+}
+
+# OWNER=jackyu TARGET=24Q3
+flag {
+    name: "minimal_telephony_managers_conditional_on_features"
+    namespace: "telephony"
+    description: "This flag enables checking for telephony features before initializing corresponding managers"
+    bug: "310710841"
+}
+
+# OWNER=joonhunshin TARGET=24Q3
+flag {
+    name: "set_no_reply_timer_for_cfnry"
+    namespace: "telephony"
+    description: "This flag supports setting the no reply timer for CFNRy based on carrier config"
+    bug:"314732435"
+}
+
+# OWNER=joonhunshin TARGET=24Q3
+flag {
+    name: "change_method_of_obtaining_ims_registration_radio_tech"
+    namespace: "telephony"
+    description: "This flag changes the method of obtaining IMS registration radio technology"
+    bug:"330120237"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+# OWNER=sasindran TARGET=24Q3
+flag {
+    name: "use_relaxed_id_match"
+    namespace: "telephony"
+    description: "This flag supports relaxed id match for radio state changes"
+    bug:"336916327"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/flags/network.aconfig b/flags/network.aconfig
index 8d6c4ac..7c09ba3 100644
--- a/flags/network.aconfig
+++ b/flags/network.aconfig
@@ -1,6 +1,7 @@
 package: "com.android.internal.telephony.flags"
 container: "system"
 
+# OWNER=nharold TARGET=24Q1
 flag {
     name: "enable_carrier_config_n1_control_attempt2"
     namespace: "telephony"
@@ -12,20 +13,25 @@
     }
 }
 
+# OWNER=sarahchin TARGET=24Q1
 flag {
   name: "hide_roaming_icon"
+  is_exported: true
   namespace: "telephony"
   description: "Allow carriers to hide the roaming (R) icon when roaming."
   bug: "301467052"
 }
 
+# OWNER=cukie TARGET=24Q3
 flag {
   name: "enable_identifier_disclosure_transparency"
+  is_exported: true
   namespace: "telephony"
   description: "Guards APIs for enabling and disabling identifier disclosure transparency"
   bug: "276752426"
 }
 
+# OWNER=cukie TARGET=24Q3
 flag {
   name: "enable_identifier_disclosure_transparency_unsol_events"
   namespace: "telephony"
@@ -33,13 +39,16 @@
   bug: "276752426"
 }
 
+# OWNER=cukie TARGET=24Q3
 flag {
   name: "enable_modem_cipher_transparency"
+  is_exported: true
   namespace: "telephony"
   description: "Guards APIs for enabling and disabling modem cipher transparency."
   bug: "283336425"
 }
 
+# OWNER=cukie TARGET=24Q3
 flag {
   name: "enable_modem_cipher_transparency_unsol_events"
   namespace: "telephony"
@@ -47,13 +56,16 @@
   bug: "283336425"
 }
 
+# OWNER=songferngwang TARGET=24Q3
 flag {
   name: "hide_prefer_3g_item"
+  is_exported: true
   namespace: "telephony"
   description: "Used in the Preferred Network Types menu to determine if the 3G option is displayed."
   bug: "310639009"
 }
 
+# OWNER=sarahchin TARGET=24Q2
 flag {
   name: "support_nr_sa_rrc_idle"
   namespace: "telephony"
@@ -61,9 +73,23 @@
   bug: "298233308"
 }
 
+# OWNER=nharold TARGET=24Q3
 flag {
   name: "network_registration_info_reject_cause"
+  is_exported: true
   namespace: "telephony"
   description: "Elevate NRI#getRejectCause from System to Public"
   bug: "239730435"
 }
+
+# OWNER=sangyun TARGET=24Q3
+flag {
+    name: "backup_and_restore_for_enable_2g"
+    namespace: "telephony"
+    description: "Support backup & restore for allow 2g (setting) option."
+    bug:"314734614"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
diff --git a/flags/satellite.aconfig b/flags/satellite.aconfig
index 4a02854..2eea80a 100644
--- a/flags/satellite.aconfig
+++ b/flags/satellite.aconfig
@@ -1,16 +1,36 @@
 package: "com.android.internal.telephony.flags"
 container: "system"
 
+# OWNER=amallampati TARGET=24Q3
 flag {
     name: "oem_enabled_satellite_flag"
+    is_exported: true
     namespace: "telephony"
     description: "This flag controls satellite communication supported by OEMs."
     bug:"291811962"
 }
 
+# OWNER=amallampati TARGET=24Q3
 flag {
     name: "carrier_enabled_satellite_flag"
+    is_exported: true
     namespace: "telephony"
     description: "This flag controls satellite communication supported by carriers."
     bug:"296437388"
 }
+
+# OWNER=nagendranb TARGET=24Q3
+flag {
+    name: "satellite_internet"
+    namespace: "telephony"
+    description: "This flag enables satellite internet support."
+    bug:"326972202"
+}
+
+# OWNER=xalle TARGET=24Q3
+flag {
+    name: "satellite_persistent_logging"
+    namespace: "telephony"
+    description: "This flag enables satellite persistent logging"
+    bug:"339877723"
+}
\ No newline at end of file
diff --git a/flags/subscription.aconfig b/flags/subscription.aconfig
index fa48fd4..9a5dabc 100644
--- a/flags/subscription.aconfig
+++ b/flags/subscription.aconfig
@@ -1,27 +1,34 @@
 package: "com.android.internal.telephony.flags"
 container: "system"
 
+# OWNER=linggm TARGET=24Q3
 flag {
   name: "work_profile_api_split"
+  is_exported: true
   namespace: "telephony"
   description: "To support separation between personal and work from TelephonyManager and SubscriptionManager API perspective."
   bug: "296076674"
 }
 
+# OWNER=linggm TARGET=24Q3
 flag {
   name: "enforce_subscription_user_filter"
+  is_exported: true
   namespace: "telephony"
   description: "Enabled flag means subscriptions enforce filtering result base on calling user handle. It marks the telephony completion of user filtering."
   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"
@@ -29,16 +36,44 @@
   bug: "296097429"
 }
 
+# OWNER=hhshin TARGET=24Q3
 flag {
   name: "support_psim_to_esim_conversion"
+  is_exported: true
   namespace: "telephony"
   description: "Support the psim to esim conversion."
   bug: "315073761"
 }
 
+# OWNER=bookatz TARGET=24Q3
 flag {
   name: "subscription_user_association_query"
+  is_exported: true
   namespace: "telephony"
   description: "Supports querying if a subscription is associated with the caller"
   bug: "325045841"
 }
+
+# OWNER=nharold TARGET=24Q3
+flag {
+  name: "safer_get_phone_number"
+  namespace: "telephony"
+  description: "Safety and performance improvements for getPhoneNumber()"
+  bug: "317673478"
+
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+# OWNER=songferngwang TARGET=24Q3
+flag {
+  name: "reset_primary_sim_default_values"
+  namespace: "telephony"
+  description: "Reset the default values to the remaining sim"
+  bug: "339394518"
+
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/flags/telephony.aconfig b/flags/telephony.aconfig
deleted file mode 100644
index d8a290d..0000000
--- a/flags/telephony.aconfig
+++ /dev/null
@@ -1,44 +0,0 @@
-package: "com.android.internal.telephony.flags"
-container: "system"
-
-flag {
-    name: "enforce_telephony_feature_mapping"
-    namespace: "telephony"
-    description: "This flag controls telephony feature flags mapping."
-    bug:"297989574"
-}
-
-flag {
-    name: "enforce_telephony_feature_mapping_for_public_apis"
-    namespace: "telephony"
-    description: "This flag controls telephony feature flags mapping for public APIs and CTS."
-    bug:"297989574"
-}
-
-flag {
-    name: "prevent_system_server_and_phone_deadlock"
-    namespace: "telephony"
-    description: "This flag controls the order of the binder to prevent deadlock in system_server"
-    bug: "315973270"
-}
-
-flag {
-    name: "prevent_invocation_repeat_of_ril_call_when_device_does_not_support_voice"
-    namespace: "telephony"
-    description: "This flag prevents repeat invocation of call related APIs in RIL when the device is not voice capable"
-    bug: "290833783"
-}
-
-flag {
-    name: "minimal_telephony_cdm_check"
-    namespace: "telephony"
-    description: "This flag disables Calling/Data/Messaging features if their respective feature is missing"
-    bug: "310710841"
-}
-
-flag {
-    name: "minimal_telephony_managers_conditional_on_features"
-    namespace: "telephony"
-    description: "This flag enables checking for telephony features before initializing corresponding managers"
-    bug: "310710841"
-}
diff --git a/flags/uicc.aconfig b/flags/uicc.aconfig
index a50f83e..2679cfe 100644
--- a/flags/uicc.aconfig
+++ b/flags/uicc.aconfig
@@ -1,33 +1,60 @@
 package: "com.android.internal.telephony.flags"
 container: "system"
 
+# OWNER=jayachandranc TARGET=24Q3
 flag {
     name: "esim_bootstrap_provisioning_flag"
     namespace: "telephony"
     description: "This flag controls eSIM Bootstrap provisioning feature support."
     bug:"298567545"
 }
+
+# OWNER=arunvoddu TARGET=24Q3
 flag {
     name: "imsi_key_retry_download_on_phone_unlock"
     namespace: "telephony"
     description: "This flag controls to download the IMSI encryption keys after user unlocks the phone."
     bug:"303780982"
 }
+
+# OWNER=arunvoddu TARGET=24Q4
 flag {
     name: "carrier_restriction_status"
+    is_exported: true
     namespace: "telephony"
     description: "This flag controls the visibility of the getCarrierRestrictionStatus in carrierRestrictionRules class."
     bug:"313553044"
 }
+
+# OWNER=arunvoddu TARGET=24Q3
 flag {
     name: "carrier_restriction_rules_enhancement"
     namespace: "telephony"
     description: "This flag controls the new enhancements to the existing carrier restrictions rules"
     bug:"317226653"
 }
+
+# OWNER=rafahs TARGET=24Q3
 flag {
     name: "esim_available_memory"
+    is_exported: true
     namespace: "telephony"
     description: "This flag controls eSIM available memory feature."
     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"
+    namespace: "telephony"
+    description: "This flag controls the visibility of the setCarrierRestrictionStatus API in carrierRestrictionRules class."
+    bug:"342411308"
+}
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto
index c07c797..48e7b0d 100644
--- a/proto/src/persist_atoms.proto
+++ b/proto/src/persist_atoms.proto
@@ -23,7 +23,7 @@
 
 // Holds atoms to store on persist storage in case of power cycle or process crash.
 // NOTE: using int64 rather than google.protobuf.Timestamp for timestamps simplifies implementation.
-// Next id: 70
+// Next id: 82
 message PersistAtoms {
     /* Aggregated RAT usage during the call. */
     repeated VoiceCallRatUsage voice_call_rat_usage = 1;
@@ -231,6 +231,42 @@
 
     /* Timestamp of last satellite_sos_message_recommender pull. */
     optional int64 satellite_sos_message_recommender_pull_timestamp_millis = 69;
+
+    /* Data Network Validation statistics and information. */
+    repeated DataNetworkValidation data_network_validation = 70;
+
+    /* Timestamp of last data_network_validation pull. */
+    optional int64 data_network_validation_pull_timestamp_millis = 71;
+
+    /* Snapshot of carrier roaming satellite session. */
+    repeated CarrierRoamingSatelliteSession carrier_roaming_satellite_session = 72;
+
+    /* Timestamp of last carrier_roaming_satellite_session pull. */
+    optional int64 carrier_roaming_satellite_session_pull_timestamp_millis = 73;
+
+    /* Snapshot of carrier roaming satellite controller stats. */
+    repeated CarrierRoamingSatelliteControllerStats carrier_roaming_satellite_controller_stats = 74;
+
+    /* Timestamp of last carrier_roaming_satellite_controller_stats pull. */
+    optional int64 carrier_roaming_satellite_controller_stats_pull_timestamp_millis = 75;
+
+    /* Snapshot of satellite entitlement. */
+    repeated SatelliteEntitlement satellite_entitlement = 76;
+
+    /* Timestamp of last satellite_entitlement pull. */
+    optional int64 satellite_entitlement_pull_timestamp_millis = 77;
+
+    /* Snapshot of satellite config updater. */
+    repeated SatelliteConfigUpdater satellite_config_updater = 78;
+
+    /* Timestamp of last satellite_config_updater pull. */
+    optional int64 satellite_config_updater_pull_timestamp_millis = 79;
+
+    /** Snapshot of satellite access controller. */
+    repeated SatelliteAccessController satellite_access_controller = 80;
+
+    /* Timestamp of last satellite access controller pull. */
+    optional int64 satellite_access_controller_pull_timestamp_millis = 81;
 }
 
 // The canonical versions of the following enums live in:
@@ -281,6 +317,9 @@
     optional bool is_iwlan_cross_sim_at_end = 38;
     optional bool is_iwlan_cross_sim_at_connected = 39;
     optional bool vonr_enabled = 40;
+    optional bool is_ntn = 41;
+    optional bool supports_business_call_composer = 42;
+    optional int32 call_composer_status = 43;
 
     // Internal use only
     optional int64 setup_begin_millis = 10001;
@@ -310,6 +349,8 @@
     optional int64 message_id = 14;
     optional int32 count = 15;
     optional bool is_managed_profile = 16;
+    optional bool is_ntn = 17;
+    optional bool is_emergency = 18;
 
     // Internal use only
     optional int32 hashCode = 10001;
@@ -334,6 +375,8 @@
     optional int32 send_error_code = 16;
     optional int32 network_error_code = 17;
     optional bool is_managed_profile = 18;
+    optional bool is_emergency = 19;
+    optional bool is_ntn = 20;
 
     // Internal use only
     optional int32 hashCode = 10001;
@@ -370,6 +413,9 @@
     repeated int32 handover_failure_rat = 21;
     optional bool is_non_dds = 22;
     optional bool is_iwlan_cross_sim = 23;
+    optional bool is_ntn = 24;
+    optional bool is_satellite_transport = 25;
+    optional bool is_provisioning_profile = 26;
 }
 
 message CellularServiceState {
@@ -388,6 +434,7 @@
     optional bool override_voice_service = 13;
     optional bool isDataEnabled = 14;
     optional bool is_iwlan_cross_sim = 15;
+    optional bool is_ntn = 16;
 
     // Internal use only
     optional int64 last_used_millis = 10001;
@@ -458,6 +505,12 @@
         PRIORITIZE_BANDWIDTH = 2;
         CBS = 3;
         ENTERPRISE = 4;
+        SATELLITE_INTERNET_RESTRICTED = 5;
+        SATELLITE_MMS_RESTRICTED = 6;
+        SATELLITE_IMS_RESTRICTED = 7;
+        SATELLITE_XCAP_RESTRICTED = 8;
+        SATELLITE_EIMS_RESTRICTED = 9;
+        SATELLITE_SUPL_RESTRICTED =10;
     }
     optional int32 carrier_id = 1;
     optional NetworkCapability capability = 2;
@@ -653,18 +706,40 @@
     optional int32 total_service_uptime_sec = 15;
     optional int32 total_battery_consumption_percent = 16;
     optional int32 total_battery_charged_time_sec = 17;
+    optional int32 count_of_demo_mode_satellite_service_enablements_success = 18;
+    optional int32 count_of_demo_mode_satellite_service_enablements_fail = 19;
+    optional int32 count_of_demo_mode_outgoing_datagram_success = 20;
+    optional int32 count_of_demo_mode_outgoing_datagram_fail = 21;
+    optional int32 count_of_demo_mode_incoming_datagram_success = 22;
+    optional int32 count_of_demo_mode_incoming_datagram_fail = 23;
+    optional int32 count_of_datagram_type_keep_alive_success = 24;
+    optional int32 count_of_datagram_type_keep_alive_fail = 25;
+    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;
 }
 
 message SatelliteSession {
     optional int32 satellite_service_initialization_result = 1;
     optional int32 satellite_technology = 2;
     optional int32 count = 3;
+    optional int32 satellite_service_termination_result = 4;
+    optional int64 initialization_processing_time_millis = 5;
+    optional int64 termination_processing_time_millis = 6;
+    optional int32 session_duration_seconds = 7;
+    optional int32 count_of_outgoing_datagram_success = 8;
+    optional int32 count_of_outgoing_datagram_failed = 9;
+    optional int32 count_of_incoming_datagram_success = 10;
+    optional int32 count_of_incoming_datagram_failed = 11;
+    optional bool is_demo_mode = 12;
+    optional int32 max_ntn_signal_strength_level = 13;
 }
 
 message SatelliteIncomingDatagram {
     optional int32 result_code = 1;
     optional int32 datagram_size_bytes = 2;
     optional int64 datagram_transfer_time_millis = 3;
+    optional bool is_demo_mode = 4;
 }
 
 message SatelliteOutgoingDatagram {
@@ -672,6 +747,7 @@
     optional int32 result_code = 2;
     optional int32 datagram_size_bytes = 3;
     optional int64 datagram_transfer_time_millis = 4;
+    optional bool is_demo_mode = 5;
 }
 
 message SatelliteProvision {
@@ -691,3 +767,69 @@
     optional int32 recommending_handover_type = 7;
     optional bool is_satellite_allowed_in_current_location = 8;
 }
+
+message DataNetworkValidation {
+    optional int32 network_type = 1;
+    optional int32 apn_type_bitmask = 2;
+    optional int32 signal_strength = 3;
+    optional int32 validation_result = 4;
+    optional int64 elapsed_time_in_millis = 5;
+    optional bool handover_attempted = 6;
+    optional int32 network_validation_count = 7;
+}
+
+message CarrierRoamingSatelliteSession {
+    optional int32 carrier_id = 1;
+    optional bool is_ntn_roaming_in_home_country = 2;
+    optional int32 total_satellite_mode_time_sec = 3;
+    optional int32 number_of_satellite_connections = 4;
+    optional int32 avg_duration_of_satellite_connection_sec = 5;
+    optional int32 satellite_connection_gap_min_sec = 6;
+    optional int32 satellite_connection_gap_avg_sec = 7;
+    optional int32 satellite_connection_gap_max_sec = 8;
+    optional int32 rsrp_avg = 9;
+    optional int32 rsrp_median = 10;
+    optional int32 rssnr_avg = 11;
+    optional int32 rssnr_median = 12;
+    optional int32 count_of_incoming_sms = 13;
+    optional int32 count_of_outgoing_sms = 14;
+    optional int32 count_of_incoming_mms = 15;
+    optional int32 count_of_outgoing_mms = 16;
+}
+
+message CarrierRoamingSatelliteControllerStats {
+    optional int32 config_data_source = 1;
+    optional int32 count_of_entitlement_status_query_request = 2;
+    optional int32 count_of_satellite_config_update_request = 3;
+    optional int32 count_of_satellite_notification_displayed = 4;
+    optional int32 satellite_session_gap_min_sec = 5;
+    optional int32 satellite_session_gap_avg_sec = 6;
+    optional int32 satellite_session_gap_max_sec = 7;
+}
+
+message SatelliteEntitlement {
+    optional int32 carrier_id = 1;
+    optional int32 result = 2;
+    optional int32 entitlement_status = 3;
+    optional bool is_retry = 4;
+    optional int32 count = 5;
+}
+
+message SatelliteConfigUpdater {
+    optional int32 config_version = 1;
+    optional int32 oem_config_result = 2;
+    optional int32 carrier_config_result = 3;
+    optional int32 count = 4;
+}
+
+message SatelliteAccessController {
+    optional int32 access_control_type = 1;
+    optional int64 location_query_time_millis = 2;
+    optional int64 on_device_lookup_time_millis = 3;
+    optional int64 total_checking_time_millis = 4;
+    optional bool is_allowed = 5;
+    optional bool is_emergency = 6;
+    optional int32 result_code = 7;
+    repeated string country_codes = 8;
+    optional int32 config_data_source = 9;
+}
diff --git a/src/java/com/android/internal/telephony/CarrierInfoManager.java b/src/java/com/android/internal/telephony/CarrierInfoManager.java
index 863db93..8364c0a 100644
--- a/src/java/com/android/internal/telephony/CarrierInfoManager.java
+++ b/src/java/com/android/internal/telephony/CarrierInfoManager.java
@@ -33,6 +33,7 @@
 import android.util.Log;
 import android.util.Pair;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 
 import java.security.PublicKey;
@@ -297,7 +298,12 @@
         final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class)
                 .createForSubscriptionId(subId);
         int carrierId = telephonyManager.getSimCarrierId();
-        deleteCarrierInfoForImsiEncryption(context, subId, carrierId);
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            String simOperator = telephonyManager.getSimOperator();
+            deleteCarrierInfoForImsiEncryption(context, subId, carrierId, simOperator);
+        } else {
+            deleteCarrierInfoForImsiEncryption(context, subId, carrierId);
+        }
         Intent resetIntent = new Intent(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD);
         SubscriptionManager.putPhoneIdAndSubIdExtra(resetIntent, mPhoneId);
         context.sendBroadcastAsUser(resetIntent, UserHandle.ALL);
@@ -312,13 +318,29 @@
      */
     public static void deleteCarrierInfoForImsiEncryption(Context context, int subId,
             int carrierId) {
+        deleteCarrierInfoForImsiEncryption(context, subId, carrierId, null);
+    }
+
+    /**
+     * Deletes all the keys for a given Carrier from the device keystore.
+     * @param context Context
+     * @param subId SubscriptionId
+     * @param carrierId delete the key which matches the carrierId
+     * @param simOperator delete the key which matches the MCCMNC
+     *
+     */
+    public static void deleteCarrierInfoForImsiEncryption(Context context, int subId,
+            int carrierId, String simOperator) {
         Log.i(LOG_TAG, "deleting carrier key from db for subId=" + subId);
         String mcc = "";
         String mnc = "";
 
-        final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class)
-                .createForSubscriptionId(subId);
-        String simOperator = telephonyManager.getSimOperator();
+        if (TextUtils.isEmpty(simOperator)) {
+            final TelephonyManager telephonyManager = context.getSystemService(
+                            TelephonyManager.class)
+                    .createForSubscriptionId(subId);
+            simOperator = telephonyManager.getSimOperator();
+        }
         if (!TextUtils.isEmpty(simOperator)) {
             mcc = simOperator.substring(0, 3);
             mnc = simOperator.substring(3);
diff --git a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
index 9143f21..10a3a32 100644
--- a/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/src/java/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -18,6 +18,7 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import android.annotation.NonNull;
 import android.app.AlarmManager;
 import android.app.DownloadManager;
 import android.app.KeyguardManager;
@@ -27,6 +28,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.database.Cursor;
+import android.net.ConnectivityManager;
+import android.net.Network;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.Message;
@@ -34,19 +37,22 @@
 import android.os.UserManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ImsiEncryptionInfo;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
 
+
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.flags.Flags;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
+
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
 import java.io.FileInputStream;
@@ -57,6 +63,7 @@
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.util.Date;
+import java.util.List;
 import java.util.Random;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.ZipException;
@@ -99,6 +106,8 @@
 
     private static final int EVENT_ALARM_OR_CONFIG_CHANGE = 0;
     private static final int EVENT_DOWNLOAD_COMPLETE = 1;
+    private static final int EVENT_NETWORK_AVAILABLE = 2;
+    private static final int EVENT_SCREEN_UNLOCKED = 3;
 
 
     private static final int[] CARRIER_KEY_TYPES = {TelephonyManager.KEY_TYPE_EPDG,
@@ -111,47 +120,77 @@
     private boolean mAllowedOverMeteredNetwork = false;
     private boolean mDeleteOldKeyAfterDownload = false;
     private boolean mIsRequiredToHandleUnlock;
-    private TelephonyManager mTelephonyManager;
+    private final TelephonyManager mTelephonyManager;
     private UserManager mUserManager;
-
     @VisibleForTesting
-    public String mMccMncForDownload;
-    public int mCarrierId;
+    public String mMccMncForDownload = "";
+    public int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
     @VisibleForTesting
     public long mDownloadId;
-    private final FeatureFlags mFeatureFlags;
+    private DefaultNetworkCallback mDefaultNetworkCallback;
+    private ConnectivityManager mConnectivityManager;
+    private KeyguardManager mKeyguardManager;
 
-    public CarrierKeyDownloadManager(Phone phone, FeatureFlags featureFlags) {
+    public CarrierKeyDownloadManager(Phone phone) {
         mPhone = phone;
-        mFeatureFlags = featureFlags;
         mContext = phone.getContext();
         IntentFilter filter = new IntentFilter();
         filter.addAction(INTENT_KEY_RENEWAL_ALARM_PREFIX);
         filter.addAction(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD);
-        filter.addAction(Intent.ACTION_USER_PRESENT);
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            filter.addAction(Intent.ACTION_USER_UNLOCKED);
+        }
         mContext.registerReceiver(mBroadcastReceiver, filter, null, phone);
         mDownloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
         mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
                 .createForSubscriptionId(mPhone.getSubId());
-        mUserManager = mContext.getSystemService(UserManager.class);
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+        } else {
+            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,
                 (slotIndex, subId, carrierId, specificCarrierId) -> {
-                    boolean isUserUnlocked = mUserManager.isUserUnlocked();
-
-                    if (isUserUnlocked && slotIndex == mPhone.getPhoneId()) {
-                        Log.d(LOG_TAG, "Carrier Config changed: slotIndex=" + slotIndex);
-                        handleAlarmOrConfigChange();
+                    if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+                        logd("CarrierConfig changed slotIndex = " + slotIndex + " subId = " + subId
+                                + " CarrierId = " + carrierId + " phoneId = "
+                                + mPhone.getPhoneId());
+                        // Below checks are necessary to optimise the logic.
+                        if ((slotIndex == mPhone.getPhoneId()) && (carrierId > 0
+                                || !TextUtils.isEmpty(
+                                mMccMncForDownload))) {
+                            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");
+                                mIsRequiredToHandleUnlock = true;
+                            } else {
+                                logd("Carrier Config changed: slotIndex=" + slotIndex);
+                                sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+                            }
+                        }
                     } else {
-                        Log.d(LOG_TAG, "User is locked");
-                        mContext.registerReceiver(mUserUnlockedReceiver, new IntentFilter(
-                                Intent.ACTION_USER_UNLOCKED));
+                        boolean isUserUnlocked = mUserManager.isUserUnlocked();
+
+                        if (isUserUnlocked && slotIndex == mPhone.getPhoneId()) {
+                            Log.d(LOG_TAG, "Carrier Config changed: slotIndex=" + slotIndex);
+                            handleAlarmOrConfigChange();
+                        } else {
+                            Log.d(LOG_TAG, "User is locked");
+                            mContext.registerReceiver(mUserUnlockedReceiver, new IntentFilter(
+                                    Intent.ACTION_USER_UNLOCKED));
+                        }
                     }
                 });
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
     }
 
+    // TODO remove this method upon imsiKeyRetryDownloadOnPhoneUnlock enabled.
     private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -167,7 +206,7 @@
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
             if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
-                Log.d(LOG_TAG, "Download Complete");
+                logd("Download Complete");
                 sendMessage(obtainMessage(EVENT_DOWNLOAD_COMPLETE,
                         intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0)));
             }
@@ -180,27 +219,36 @@
             String action = intent.getAction();
             int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
             int phoneId = mPhone.getPhoneId();
-            if (action.equals(INTENT_KEY_RENEWAL_ALARM_PREFIX)) {
-                int slotIndexExtra = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, -1);
-                if (slotIndexExtra == slotIndex) {
-                    Log.d(LOG_TAG, "Handling key renewal alarm: " + action);
-                    sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+            switch (action) {
+                case INTENT_KEY_RENEWAL_ALARM_PREFIX -> {
+                    int slotIndexExtra = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
+                            -1);
+                    if (slotIndexExtra == slotIndex) {
+                        logd("Handling key renewal alarm: " + action);
+                        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+                            updateSimOperator();
+                        }
+                        sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+                    }
                 }
-            } else if (action.equals(TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD)) {
-                if (phoneId == intent.getIntExtra(PhoneConstants.PHONE_KEY,
-                        SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
-                    Log.d(LOG_TAG, "Handling reset intent: " + action);
-                    sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+                case TelephonyIntents.ACTION_CARRIER_CERTIFICATE_DOWNLOAD -> {
+                    if (phoneId == intent.getIntExtra(PhoneConstants.PHONE_KEY,
+                            SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
+                        logd("Handling reset intent: " + action);
+                        sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+                    }
                 }
-            }  else if (action.equals(Intent.ACTION_USER_PRESENT)) {
-                // The Carrier key download fails when SIM is inserted while device is locked
-                // hence adding a retry logic when device is unlocked.
-                Log.d(LOG_TAG,
-                        "device unlocked, isRequiredToHandleUnlock = " + mIsRequiredToHandleUnlock
-                                + ", slotIndex = " + slotIndex);
-                if (mIsRequiredToHandleUnlock) {
-                    mIsRequiredToHandleUnlock = false;
-                    sendEmptyMessage(EVENT_ALARM_OR_CONFIG_CHANGE);
+                case Intent.ACTION_USER_UNLOCKED -> {
+                    // The Carrier key download fails when SIM is inserted while device is locked
+                    // hence adding a retry logic when device is unlocked.
+                    logd("device fully unlocked, isRequiredToHandleUnlock = "
+                            + mIsRequiredToHandleUnlock
+                            + ", slotIndex = " + slotIndex + "  hasActiveDataNetwork = " + (
+                            mConnectivityManager.getActiveNetwork() != null));
+                    if (mIsRequiredToHandleUnlock) {
+                        mIsRequiredToHandleUnlock = false;
+                        sendEmptyMessage(EVENT_SCREEN_UNLOCKED);
+                    }
                 }
             }
         }
@@ -209,76 +257,124 @@
     @Override
     public void handleMessage (Message msg) {
         switch (msg.what) {
-            case EVENT_ALARM_OR_CONFIG_CHANGE:
-                handleAlarmOrConfigChange();
-                break;
-            case EVENT_DOWNLOAD_COMPLETE:
+            case EVENT_ALARM_OR_CONFIG_CHANGE, EVENT_NETWORK_AVAILABLE, EVENT_SCREEN_UNLOCKED ->
+                    handleAlarmOrConfigChange();
+            case EVENT_DOWNLOAD_COMPLETE -> {
                 long carrierKeyDownloadIdentifier = (long) msg.obj;
-                String currentMccMnc = getSimOperator();
-                int carrierId = getSimCarrierId();
+                String currentMccMnc = Flags.imsiKeyRetryDownloadOnPhoneUnlock()
+                        ? mTelephonyManager.getSimOperator(mPhone.getSubId()) : getSimOperator();
+                int carrierId = Flags.imsiKeyRetryDownloadOnPhoneUnlock()
+                        ? mTelephonyManager.getSimCarrierId() : getSimCarrierId();
                 if (isValidDownload(currentMccMnc, carrierKeyDownloadIdentifier, carrierId)) {
                     onDownloadComplete(carrierKeyDownloadIdentifier, currentMccMnc, carrierId);
-                    onPostDownloadProcessing(carrierKeyDownloadIdentifier);
+                    onPostDownloadProcessing();
                 }
-                break;
+            }
         }
     }
 
-    private void onPostDownloadProcessing(long carrierKeyDownloadIdentifier) {
+    private void onPostDownloadProcessing() {
         resetRenewalAlarm();
-        cleanupDownloadInfo();
-
+        if(Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            mDownloadId = -1;
+        } else {
+            cleanupDownloadInfo();
+        }
         // unregister from DOWNLOAD_COMPLETE
         mContext.unregisterReceiver(mDownloadReceiver);
     }
 
     private void handleAlarmOrConfigChange() {
-        if (carrierUsesKeys()) {
-            if (areCarrierKeysAbsentOrExpiring()) {
-                boolean downloadStartedSuccessfully = downloadKey();
-                // if the download was attempted, but not started successfully, and if carriers uses
-                // keys, we'll still want to renew the alarms, and try downloading the key a day
-                // later.
-                if (!downloadStartedSuccessfully) {
-                    // If download fails due to the device lock, we will reattempt once the device
-                    // is unlocked.
-                    if (mFeatureFlags.imsiKeyRetryDownloadOnPhoneUnlock()) {
-                        KeyguardManager keyguardManager = mContext.getSystemService(
-                                KeyguardManager.class);
-                        if (keyguardManager.isKeyguardSecure()) {
-                            Log.e(LOG_TAG, "Key download failed in device lock state");
-                            mIsRequiredToHandleUnlock = true;
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            if (carrierUsesKeys()) {
+                if (areCarrierKeysAbsentOrExpiring()) {
+                    boolean hasActiveDataNetwork =
+                            (mConnectivityManager.getActiveNetwork() != null);
+                    boolean downloadStartedSuccessfully = hasActiveDataNetwork && downloadKey();
+                    // if the download was attempted, but not started successfully, and if
+                    // carriers uses keys, we'll still want to renew the alarms, and try
+                    // downloading the key a day later.
+                    int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
+                    if (downloadStartedSuccessfully) {
+                        unregisterDefaultNetworkCb(slotIndex);
+                    } else {
+                        // If download fails due to the device lock, we will reattempt once the
+                        // device is unlocked.
+                        mIsRequiredToHandleUnlock = mKeyguardManager.isDeviceLocked();
+                        loge("hasActiveDataConnection = " + hasActiveDataNetwork
+                                + "    isDeviceLocked = " + mIsRequiredToHandleUnlock);
+                        if (!hasActiveDataNetwork) {
+                            registerDefaultNetworkCb(slotIndex);
                         }
+                        resetRenewalAlarm();
                     }
-                    resetRenewalAlarm();
                 }
+                logd("handleAlarmOrConfigChange :: areCarrierKeysAbsentOrExpiring returned false");
             } else {
-                return;
+                cleanupRenewalAlarms();
+                if (!isOtherSlotHasCarrier()) {
+                    // delete any existing alarms.
+                    mPhone.deleteCarrierInfoForImsiEncryption(getSimCarrierId(), getSimOperator());
+                }
+                cleanupDownloadInfo();
             }
         } else {
-            // delete any existing alarms.
-            cleanupRenewalAlarms();
-            mPhone.deleteCarrierInfoForImsiEncryption(getSimCarrierId());
-
+            if (carrierUsesKeys()) {
+                if (areCarrierKeysAbsentOrExpiring()) {
+                    boolean downloadStartedSuccessfully = downloadKey();
+                    // if the download was attempted, but not started successfully, and if
+                    // carriers uses keys, we'll still want to renew the alarms, and try
+                    // downloading the key a day later.
+                    if (!downloadStartedSuccessfully) {
+                        resetRenewalAlarm();
+                    }
+                }
+            } else {
+                // delete any existing alarms.
+                cleanupRenewalAlarms();
+                mPhone.deleteCarrierInfoForImsiEncryption(getSimCarrierId());
+            }
         }
     }
 
+    private boolean isOtherSlotHasCarrier() {
+        SubscriptionManager subscriptionManager = mPhone.getContext().getSystemService(
+                SubscriptionManager.class);
+        List<SubscriptionInfo> subscriptionInfoList =
+                subscriptionManager.getActiveSubscriptionInfoList();
+        loge("handleAlarmOrConfigChange ActiveSubscriptionInfoList = " + (
+                (subscriptionInfoList != null) ? subscriptionInfoList.size() : null));
+        for (SubscriptionInfo subInfo : subscriptionInfoList) {
+            if (mPhone.getSubId() != subInfo.getSubscriptionId()
+                    && subInfo.getCarrierId() == mPhone.getCarrierId()) {
+                // We do not proceed to remove the Key from the DB as another slot contains
+                // same operator sim which is in active state.
+                loge("handleAlarmOrConfigChange same operator sim in another slot");
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void cleanupDownloadInfo() {
-        Log.d(LOG_TAG, "Cleaning up download info");
+        logd("Cleaning up download info");
         mDownloadId = -1;
-        mMccMncForDownload = null;
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            mMccMncForDownload = "";
+        } else {
+            mMccMncForDownload = null;
+        }
         mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
     }
 
     private void cleanupRenewalAlarms() {
-        Log.d(LOG_TAG, "Cleaning up existing renewal alarms");
+        logd("Cleaning up existing renewal alarms");
         int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
         Intent intent = new Intent(INTENT_KEY_RENEWAL_ALARM_PREFIX);
         intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, slotIndex);
         PendingIntent carrierKeyDownloadIntent = PendingIntent.getBroadcast(mContext, 0, intent,
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-        AlarmManager alarmManager =
-                (AlarmManager) mContext.getSystemService(mContext.ALARM_SERVICE);
+        AlarmManager alarmManager =mContext.getSystemService(AlarmManager.class);
         alarmManager.cancel(carrierKeyDownloadIntent);
     }
 
@@ -331,7 +427,7 @@
         cleanupRenewalAlarms();
         int slotIndex = SubscriptionManager.getSlotIndex(mPhone.getSubId());
         long minExpirationDate = getExpirationDate();
-        Log.d(LOG_TAG, "minExpirationDate: " + new Date(minExpirationDate));
+        logd("minExpirationDate: " + new Date(minExpirationDate));
         final AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
                 Context.ALARM_SERVICE);
         Intent intent = new Intent(INTENT_KEY_RENEWAL_ALARM_PREFIX);
@@ -339,16 +435,35 @@
         PendingIntent carrierKeyDownloadIntent = PendingIntent.getBroadcast(mContext, 0, intent,
                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
         alarmManager.set(AlarmManager.RTC_WAKEUP, minExpirationDate, carrierKeyDownloadIntent);
-        Log.d(LOG_TAG, "setRenewalAlarm: action=" + intent.getAction() + " time="
+        logd("setRenewalAlarm: action=" + intent.getAction() + " time="
                 + new Date(minExpirationDate));
     }
 
     /**
+     * Read the store the sim operetor value and update the value in case of change in the sim
+     * operetor.
+     */
+    public void updateSimOperator() {
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            String simOperator = mPhone.getOperatorNumeric();
+            if (!TextUtils.isEmpty(simOperator) && !simOperator.equals(mMccMncForDownload)) {
+                mMccMncForDownload = simOperator;
+                logd("updateSimOperator, Initialized mMccMncForDownload = " + mMccMncForDownload);
+            }
+        }
+    }
+
+    /**
      * Returns the sim operator.
      **/
     @VisibleForTesting
     public String getSimOperator() {
-        return mTelephonyManager.getSimOperator(mPhone.getSubId());
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            updateSimOperator();
+            return mMccMncForDownload;
+        } else {
+            return mTelephonyManager.getSimOperator(mPhone.getSubId());
+        }
     }
 
     /**
@@ -356,7 +471,11 @@
      **/
     @VisibleForTesting
     public int getSimCarrierId() {
-        return mTelephonyManager.getSimCarrierId();
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            return (mCarrierId > 0) ? mCarrierId : mPhone.getCarrierId();
+        } else {
+            return mTelephonyManager.getSimCarrierId();
+        }
     }
 
     /**
@@ -367,7 +486,7 @@
     @VisibleForTesting
     public boolean isValidDownload(String currentMccMnc, long currentDownloadId, int carrierId) {
         if (currentDownloadId != mDownloadId) {
-            Log.e(LOG_TAG, "download ID=" + currentDownloadId
+            loge( "download ID=" + currentDownloadId
                     + " for completed download does not match stored id=" + mDownloadId);
             return false;
         }
@@ -375,12 +494,12 @@
         if (TextUtils.isEmpty(currentMccMnc) || TextUtils.isEmpty(mMccMncForDownload)
                 || !TextUtils.equals(currentMccMnc, mMccMncForDownload)
                 || mCarrierId != carrierId) {
-            Log.e(LOG_TAG, "currentMccMnc=" + currentMccMnc + " storedMccMnc =" + mMccMncForDownload
+            loge( "currentMccMnc=" + currentMccMnc + " storedMccMnc =" + mMccMncForDownload
                     + "currentCarrierId = " + carrierId + "  storedCarrierId = " + mCarrierId);
             return false;
         }
 
-        Log.d(LOG_TAG, "Matched MccMnc =  " + currentMccMnc + ", carrierId = " + carrierId
+        logd("Matched MccMnc =  " + currentMccMnc + ", carrierId = " + carrierId
                 + ", downloadId: " + currentDownloadId);
         return true;
     }
@@ -390,7 +509,7 @@
      **/
     private void onDownloadComplete(long carrierKeyDownloadIdentifier, String mccMnc,
             int carrierId) {
-        Log.d(LOG_TAG, "onDownloadComplete: " + carrierKeyDownloadIdentifier);
+        logd("onDownloadComplete: " + carrierKeyDownloadIdentifier);
         String jsonStr;
         DownloadManager.Query query = new DownloadManager.Query();
         query.setFilterById(carrierKeyDownloadIdentifier);
@@ -405,22 +524,21 @@
                 try {
                     jsonStr = convertToString(mDownloadManager, carrierKeyDownloadIdentifier);
                     if (TextUtils.isEmpty(jsonStr)) {
-                        Log.d(LOG_TAG, "fallback to no gzip");
+                        logd("fallback to no gzip");
                         jsonStr = convertToStringNoGZip(mDownloadManager,
                                 carrierKeyDownloadIdentifier);
                     }
                     parseJsonAndPersistKey(jsonStr, mccMnc, carrierId);
                 } catch (Exception e) {
-                    Log.e(LOG_TAG, "Error in download:" + carrierKeyDownloadIdentifier
+                    loge( "Error in download:" + carrierKeyDownloadIdentifier
                             + ". " + e);
                 } finally {
                     mDownloadManager.remove(carrierKeyDownloadIdentifier);
                 }
             }
-            Log.d(LOG_TAG, "Completed downloading keys");
+            logd("Completed downloading keys");
         }
         cursor.close();
-        return;
     }
 
     /**
@@ -442,7 +560,7 @@
                     CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING,
                     CarrierConfigManager.KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL);
         } catch (RuntimeException e) {
-            Log.e(LOG_TAG, "CarrierConfigLoader is not available.");
+            loge( "CarrierConfigLoader is not available.");
         }
         if (b == null || b.isEmpty()) {
             return false;
@@ -452,10 +570,10 @@
         mURL = b.getString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING);
         mAllowedOverMeteredNetwork = b.getBoolean(
                 CarrierConfigManager.KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL);
+
         if (mKeyAvailability == 0 || TextUtils.isEmpty(mURL)) {
-            Log.d(LOG_TAG,
-                    "Carrier not enabled or invalid values. mKeyAvailability=" + mKeyAvailability
-                            + " mURL=" + mURL);
+            logd("Carrier not enabled or invalid values. mKeyAvailability=" + mKeyAvailability
+                    + " mURL=" + mURL);
             return false;
         }
         for (int key_type : CARRIER_KEY_TYPES) {
@@ -469,7 +587,7 @@
     private static String convertToStringNoGZip(DownloadManager downloadManager, long downloadId) {
         StringBuilder sb = new StringBuilder();
         try (InputStream source = new FileInputStream(
-                    downloadManager.openDownloadedFile(downloadId).getFileDescriptor())) {
+                downloadManager.openDownloadedFile(downloadId).getFileDescriptor())) {
             // If the carrier does not have the data gzipped, fallback to assuming it is not zipped.
             // parseJsonAndPersistKey may still fail if the data is malformed, so we won't be
             // persisting random bogus strings thinking it's the cert
@@ -488,8 +606,8 @@
 
     private static String convertToString(DownloadManager downloadManager, long downloadId) {
         try (InputStream source = new FileInputStream(
-                    downloadManager.openDownloadedFile(downloadId).getFileDescriptor());
-                    InputStream gzipIs = new GZIPInputStream(source)) {
+                downloadManager.openDownloadedFile(downloadId).getFileDescriptor());
+             InputStream gzipIs = new GZIPInputStream(source)) {
             BufferedReader reader = new BufferedReader(new InputStreamReader(gzipIs, UTF_8));
             StringBuilder sb = new StringBuilder();
 
@@ -523,7 +641,7 @@
     public void parseJsonAndPersistKey(String jsonStr, String mccMnc, int carrierId) {
         if (TextUtils.isEmpty(jsonStr) || TextUtils.isEmpty(mccMnc)
                 || carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
-            Log.e(LOG_TAG, "jsonStr or mcc, mnc: is empty or carrierId is UNKNOWN_CARRIER_ID");
+            loge( "jsonStr or mcc, mnc: is empty or carrierId is UNKNOWN_CARRIER_ID");
             return;
         }
         try {
@@ -548,22 +666,28 @@
                     if (typeString.equals(JSON_TYPE_VALUE_EPDG)) {
                         type = TelephonyManager.KEY_TYPE_EPDG;
                     } else if (!typeString.equals(JSON_TYPE_VALUE_WLAN)) {
-                        Log.e(LOG_TAG, "Invalid key-type specified: " + typeString);
+                        loge( "Invalid key-type specified: " + typeString);
                     }
                 }
                 String identifier = key.getString(JSON_IDENTIFIER);
                 Pair<PublicKey, Long> keyInfo =
                         getKeyInformation(cleanCertString(cert).getBytes());
                 if (mDeleteOldKeyAfterDownload) {
-                    mPhone.deleteCarrierInfoForImsiEncryption(TelephonyManager.UNKNOWN_CARRIER_ID);
+                    if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+                        mPhone.deleteCarrierInfoForImsiEncryption(
+                                TelephonyManager.UNKNOWN_CARRIER_ID, null);
+                    } else {
+                        mPhone.deleteCarrierInfoForImsiEncryption(
+                                TelephonyManager.UNKNOWN_CARRIER_ID);
+                    }
                     mDeleteOldKeyAfterDownload = false;
                 }
                 savePublicKey(keyInfo.first, type, identifier, keyInfo.second, mcc, mnc, carrierId);
             }
         } catch (final JSONException e) {
-            Log.e(LOG_TAG, "Json parsing error: " + e.getMessage());
+            loge( "Json parsing error: " + e.getMessage());
         } catch (final Exception e) {
-            Log.e(LOG_TAG, "Exception getting certificate: " + e);
+            loge( "Exception getting certificate: " + e);
         }
     }
 
@@ -584,7 +708,7 @@
     public static boolean isKeyEnabled(int keyType, int keyAvailability) {
         // since keytype has values of 1, 2.... we need to subtract 1 from the keytype.
         int returnValue = (keyAvailability >> (keyType - 1)) & 1;
-        return (returnValue == 1) ? true : false;
+        return returnValue == 1;
     }
 
     /**
@@ -603,31 +727,42 @@
             ImsiEncryptionInfo imsiEncryptionInfo =
                     mPhone.getCarrierInfoForImsiEncryption(key_type, false);
             if (imsiEncryptionInfo == null) {
-                Log.d(LOG_TAG, "Key not found for: " + key_type);
+                logd("Key not found for: " + key_type);
                 return true;
             } else if (imsiEncryptionInfo.getCarrierId() == TelephonyManager.UNKNOWN_CARRIER_ID) {
-                Log.d(LOG_TAG, "carrier key is unknown carrier, so prefer to reDownload");
+                logd("carrier key is unknown carrier, so prefer to reDownload");
                 mDeleteOldKeyAfterDownload = true;
                 return true;
             }
             Date imsiDate = imsiEncryptionInfo.getExpirationTime();
             long timeToExpire = imsiDate.getTime() - System.currentTimeMillis();
-            return (timeToExpire < START_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS) ? true : false;
+            return timeToExpire < START_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS;
         }
         return false;
     }
 
     private boolean downloadKey() {
-        Log.d(LOG_TAG, "starting download from: " + mURL);
-        String mccMnc = getSimOperator();
-        int carrierId = getSimCarrierId();
-        if (!TextUtils.isEmpty(mccMnc) || carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
-            Log.d(LOG_TAG, "downloading key for mccmnc : " + mccMnc + ", carrierId : "
-                    + carrierId);
+        logd("starting download from: " + mURL);
+        String mccMnc = null;
+        int carrierId = -1;
+        if (Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+            if (TextUtils.isEmpty(mMccMncForDownload)
+                    || mCarrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
+                loge("mccmnc or carrierId is UnKnown");
+                return false;
+            }
         } else {
-            Log.e(LOG_TAG, "mccmnc or carrierId is UnKnown");
-            return false;
+            mccMnc = getSimOperator();
+            carrierId = getSimCarrierId();
+            if (!TextUtils.isEmpty(mccMnc) || carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
+                Log.d(LOG_TAG, "downloading key for mccmnc : " + mccMnc + ", carrierId : "
+                        + carrierId);
+            } else {
+                Log.e(LOG_TAG, "mccmnc or carrierId is UnKnown");
+                return false;
+            }
         }
+
         try {
             // register the broadcast receiver to listen for download complete
             IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
@@ -641,15 +776,16 @@
             request.setAllowedOverMetered(mAllowedOverMeteredNetwork);
             request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
             request.addRequestHeader("Accept-Encoding", "gzip");
-            Long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request);
-
-            Log.d(LOG_TAG, "saving values mccmnc: " + mccMnc + ", downloadId: "
-                    + carrierKeyDownloadRequestId + ", carrierId: " + carrierId);
-            mMccMncForDownload = mccMnc;
-            mCarrierId = carrierId;
+            long carrierKeyDownloadRequestId = mDownloadManager.enqueue(request);
+            if (!Flags.imsiKeyRetryDownloadOnPhoneUnlock()) {
+                mMccMncForDownload = mccMnc;
+                mCarrierId = carrierId;
+            }
             mDownloadId = carrierKeyDownloadRequestId;
+            logd("saving values mccmnc: " + mMccMncForDownload + ", downloadId: "
+                    + carrierKeyDownloadRequestId + ", carrierId: " + mCarrierId);
         } catch (Exception e) {
-            Log.e(LOG_TAG, "exception trying to download key from url: " + mURL + ", Exception = "
+            loge( "exception trying to download key from url: " + mURL + ",  Exception = "
                     + e.getMessage());
             return false;
         }
@@ -667,7 +803,7 @@
         CertificateFactory cf = CertificateFactory.getInstance("X.509");
         X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
         Pair<PublicKey, Long> keyInformation =
-                new Pair(cert.getPublicKey(), cert.getNotAfter().getTime());
+                new Pair<>(cert.getPublicKey(), cert.getNotAfter().getTime());
         return keyInformation;
     }
 
@@ -699,4 +835,59 @@
                 cert.indexOf(CERT_BEGIN_STRING),
                 cert.indexOf(CERT_END_STRING) + CERT_END_STRING.length());
     }
+
+    /**
+     * Registering the callback to listen on data connection availability.
+     *
+     * @param slotId Sim slotIndex that tries to download the key.
+     */
+    private void registerDefaultNetworkCb(int slotId) {
+        logd("RegisterDefaultNetworkCb for slotId = " + slotId);
+        if (mDefaultNetworkCallback == null
+                && slotId != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
+            mDefaultNetworkCallback = new DefaultNetworkCallback(slotId);
+            mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
+        }
+    }
+
+    /**
+     * Unregister the data connection monitor listener.
+     */
+    private void unregisterDefaultNetworkCb(int slotId) {
+        logd("unregisterDefaultNetworkCb for slotId = " + slotId);
+        if (mDefaultNetworkCallback != null) {
+            mConnectivityManager.unregisterNetworkCallback(mDefaultNetworkCallback);
+            mDefaultNetworkCallback = null;
+        }
+    }
+
+    final class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback {
+        final int mSlotIndex;
+
+        public DefaultNetworkCallback(int slotId) {
+            mSlotIndex = slotId;
+        }
+
+        /** Called when the framework connects and has declared a new network ready for use. */
+        @Override
+        public void onAvailable(@NonNull Network network) {
+            logd("Data network connected, slotId = " + mSlotIndex);
+            if (mConnectivityManager.getActiveNetwork() != null && SubscriptionManager.getSlotIndex(
+                    mPhone.getSubId()) == mSlotIndex) {
+                mIsRequiredToHandleUnlock = false;
+                unregisterDefaultNetworkCb(mSlotIndex);
+                sendEmptyMessage(EVENT_NETWORK_AVAILABLE);
+            }
+        }
+    }
+
+    private void loge(String logStr) {
+        String TAG = LOG_TAG + " [" + mPhone.getPhoneId() + "]";
+        Log.e(TAG, logStr);
+    }
+
+    private void logd(String logStr) {
+        String TAG = LOG_TAG + " [" + mPhone.getPhoneId() + "]";
+        Log.d(TAG, logStr);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/Connection.java b/src/java/com/android/internal/telephony/Connection.java
index 68fd6ab..82b4c2b8e 100644
--- a/src/java/com/android/internal/telephony/Connection.java
+++ b/src/java/com/android/internal/telephony/Connection.java
@@ -33,6 +33,7 @@
 
 import com.android.ims.internal.ConferenceParticipant;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.domainselection.DomainSelectionResolver;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
@@ -634,7 +635,7 @@
      *
      * @hide
      */
-    public void setEmergencyCallInfo(CallTracker ct) {
+    public void setEmergencyCallInfo(CallTracker ct, Phone.DialArgs dialArgs) {
         if (ct != null) {
             Phone currentPhone = ct.getPhone();
             if (currentPhone != null) {
@@ -677,20 +678,52 @@
         } else {
             Rlog.e(TAG, "setEmergencyCallInfo: call tracker is null");
         }
+
+        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+            if (mEmergencyNumberInfo == null) {
+                Rlog.d(TAG, "setEmergencyCallInfo: create EmergencyNumber");
+                setNonDetectableEmergencyCallInfo((dialArgs != null) ? dialArgs.eccCategory
+                        : EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED,
+                        new ArrayList<String>());
+            }
+            if (dialArgs != null && dialArgs.intentExtras != null
+                    && dialArgs.intentExtras.getBoolean(
+                            PhoneConstants.EXTRA_USE_EMERGENCY_ROUTING, false)
+                    && mEmergencyNumberInfo.getEmergencyCallRouting()
+                        != EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY) {
+                int eccCategory = dialArgs.intentExtras.getInt(
+                        PhoneConstants.EXTRA_EMERGENCY_SERVICE_CATEGORY,
+                        mEmergencyNumberInfo.getEmergencyServiceCategoryBitmask());
+                Rlog.d(TAG, "setEmergencyCallInfo: enforce emergency routing eccCategory="
+                        + eccCategory);
+                List<String> emergencyUrns = dialArgs.intentExtras.getStringArrayList(
+                        PhoneConstants.EXTRA_EMERGENCY_URNS);
+                if (emergencyUrns == null || emergencyUrns.isEmpty()) {
+                    emergencyUrns = mEmergencyNumberInfo.getEmergencyUrns();
+                }
+                mEmergencyNumberInfo = new EmergencyNumber(mEmergencyNumberInfo.getNumber(),
+                        mEmergencyNumberInfo.getCountryIso(),
+                        mEmergencyNumberInfo.getMnc(),
+                        eccCategory,
+                        emergencyUrns,
+                        mEmergencyNumberInfo.getEmergencyNumberSourceBitmask(),
+                        EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
+            }
+        }
     }
 
     /**
      * Set the non-detectable emergency number information.
      */
-    public void setNonDetectableEmergencyCallInfo(int eccCategory) {
-        if (!mIsEmergencyCall) {
-            mIsEmergencyCall = true;
-            mEmergencyNumberInfo = new EmergencyNumber(mAddress, ""/*countryIso*/,
-                                    ""/*mnc*/, eccCategory,
-                                    new ArrayList<String>(),
-                                    EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
-                                    EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
-        }
+    public void setNonDetectableEmergencyCallInfo(int eccCategory,
+            @NonNull List<String> emergencyUrns) {
+        Rlog.d(TAG, "setNonDetectableEmergencyCallInfo: eccCategory=" + eccCategory
+                + ", emergencyUrns=" + emergencyUrns);
+        mIsEmergencyCall = true;
+        mEmergencyNumberInfo = new EmergencyNumber(mAddress, ""/*countryIso*/, ""/*mnc*/,
+                                eccCategory, emergencyUrns,
+                                EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING,
+                                EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN);
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
index e81d0f1..9c7993b 100644
--- a/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -318,6 +318,12 @@
         mTelephonyRegistryMgr.notifyCallbackModeStopped(sender.getPhoneId(),
                 sender.getSubId(), type, reason);
     }
+
+    @Override
+    public void notifyCarrierRoamingNtnModeChanged(Phone sender, boolean active) {
+        mTelephonyRegistryMgr.notifyCarrierRoamingNtnModeChanged(sender.getSubId(), active);
+    }
+
     /**
      * 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/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
index 9113514..26d4e1b 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
@@ -160,7 +160,7 @@
     public GsmCdmaCallTracker(GsmCdmaPhone phone, FeatureFlags featureFlags) {
         super(featureFlags);
 
-        if (mFeatureFlags.minimalTelephonyCdmCheck()
+        if (TelephonyCapabilities.minimalTelephonyCdmCheck(mFeatureFlags)
                 && !phone.getContext().getPackageManager().hasSystemFeature(
                     PackageManager.FEATURE_TELEPHONY_CALLING)) {
             throw new UnsupportedOperationException("GsmCdmaCallTracker requires calling");
diff --git a/src/java/com/android/internal/telephony/GsmCdmaConnection.java b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
index e06520a..cc07047 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaConnection.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaConnection.java
@@ -139,7 +139,7 @@
         mHandler = new MyHandler(mOwner.getLooper());
 
         mAddress = dc.number;
-        setEmergencyCallInfo(mOwner);
+        setEmergencyCallInfo(mOwner, null);
 
         String forwardedNumber = TextUtils.isEmpty(dc.forwardedNumber) ? null : dc.forwardedNumber;
         Rlog.i(LOG_TAG, "create, forwardedNumber=" + Rlog.pii(LOG_TAG, forwardedNumber));
@@ -186,13 +186,13 @@
 
         mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
         if (dialArgs.isEmergency) {
-            setEmergencyCallInfo(mOwner);
+            setEmergencyCallInfo(mOwner, null);
 
             // 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
             // non-detectable emergency call from IMS.
             if (getEmergencyNumberInfo() == null) {
-                setNonDetectableEmergencyCallInfo(dialArgs.eccCategory);
+                setNonDetectableEmergencyCallInfo(dialArgs.eccCategory, new ArrayList<String>());
             }
         }
 
diff --git a/src/java/com/android/internal/telephony/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index 4c7a3d3..93a0c2f 100644
--- a/src/java/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
@@ -411,7 +411,7 @@
 
         mLinkBandwidthEstimator = mTelephonyComponentFactory
                 .inject(LinkBandwidthEstimator.class.getName())
-                .makeLinkBandwidthEstimator(this);
+                .makeLinkBandwidthEstimator(this, getLooper());
 
         mCallWaitingController = new CallWaitingController(this);
 
@@ -470,7 +470,7 @@
     };
 
     private boolean hasCalling() {
-        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        if (!TelephonyCapabilities.minimalTelephonyCdmCheck(mFeatureFlags)) return true;
         return mContext.getPackageManager().hasSystemFeature(
             PackageManager.FEATURE_TELEPHONY_CALLING);
     }
@@ -539,7 +539,7 @@
         mContext.registerReceiver(mBroadcastReceiver, filter,
                 android.Manifest.permission.MODIFY_PHONE_STATE, null, Context.RECEIVER_EXPORTED);
 
-        mCDM = new CarrierKeyDownloadManager(this, mFeatureFlags);
+        mCDM = new CarrierKeyDownloadManager(this);
         mCIM = new CarrierInfoManager();
 
         mCi.registerForImeiMappingChanged(this, EVENT_IMEI_MAPPING_CHANGED, null);
@@ -2124,8 +2124,13 @@
 
     @Override
     public void deleteCarrierInfoForImsiEncryption(int carrierId) {
+        CarrierInfoManager.deleteCarrierInfoForImsiEncryption(mContext, getSubId(), carrierId);
+    }
+
+    @Override
+    public void deleteCarrierInfoForImsiEncryption(int carrierId, String simOperator) {
         CarrierInfoManager.deleteCarrierInfoForImsiEncryption(mContext, getSubId(),
-                carrierId);
+                carrierId, simOperator);
     }
 
     @Override
@@ -2298,6 +2303,11 @@
     }
 
     @Override
+    protected void onSetNetworkSelectionModeCompleted() {
+        mSST.pollState();
+    }
+
+    @Override
     public String getCdmaPrlVersion() {
         return mSST.getPrlVersion();
     }
@@ -3029,12 +3039,12 @@
 
     @Override
     public void registerForCallWaiting(Handler h, int what, Object obj) {
-        mCT.registerForCallWaiting(h, what, obj);
+        if (mCT != null) mCT.registerForCallWaiting(h, what, obj);
     }
 
     @Override
     public void unregisterForCallWaiting(Handler h) {
-        mCT.unregisterForCallWaiting(h);
+        if (mCT != null) mCT.unregisterForCallWaiting(h);
     }
 
     /**
@@ -3669,6 +3679,7 @@
             case EVENT_SUBSCRIPTIONS_CHANGED:
                 logd("EVENT_SUBSCRIPTIONS_CHANGED");
                 updateUsageSetting();
+                updateNullCipherNotifier();
                 break;
             case EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE:
                 logd("EVENT_SET_NULL_CIPHER_AND_INTEGRITY_DONE");
@@ -3769,7 +3780,8 @@
                         && mNullCipherNotifier != null) {
                     ar = (AsyncResult) msg.obj;
                     SecurityAlgorithmUpdate update = (SecurityAlgorithmUpdate) ar.result;
-                    mNullCipherNotifier.onSecurityAlgorithmUpdate(mContext, getSubId(), update);
+                    mNullCipherNotifier.onSecurityAlgorithmUpdate(mContext, getPhoneId(),
+                            getSubId(), update);
                 }
                 break;
 
@@ -4175,6 +4187,10 @@
             Rlog.d(LOG_TAG, "exitEmergencyCallbackMode: mImsPhone=" + mImsPhone
                     + " isPhoneTypeGsm=" + isPhoneTypeGsm());
         }
+        if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
+            EmergencyStateTracker.getInstance().exitEmergencyCallbackMode();
+            return;
+        }
         if (mImsPhone != null && mImsPhone.isInImsEcm()) {
             mImsPhone.exitEmergencyCallbackMode();
         } else {
@@ -5440,6 +5456,25 @@
                 obtainMessage(EVENT_SET_SECURITY_ALGORITHMS_UPDATED_ENABLED_DONE));
     }
 
+    /**
+     * Update the phoneId -> subId mapping of the null cipher notifier.
+     */
+    @VisibleForTesting
+    public void updateNullCipherNotifier() {
+        if (!mFeatureFlags.enableModemCipherTransparencyUnsolEvents()) {
+            return;
+        }
+
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerService
+                .getSubscriptionInfoInternal(getSubId());
+        boolean active = false;
+        if (subInfo != null) {
+            active = subInfo.isActive();
+        }
+        mNullCipherNotifier.setSubscriptionMapping(mContext, getPhoneId(),
+                active ? subInfo.getSubscriptionId() : -1);
+    }
+
     @Override
     public boolean isNullCipherAndIntegritySupported() {
         return mIsNullCipherAndIntegritySupported;
diff --git a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
index 4146c24..1a6bf2b 100644
--- a/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
+++ b/src/java/com/android/internal/telephony/ImsSmsDispatcher.java
@@ -203,7 +203,8 @@
                         mTrackers.remove(token);
                         mPhone.notifySmsSent(tracker.mDestAddress);
                         mSmsDispatchersController.notifySmsSentToEmergencyStateTracker(
-                                tracker.mDestAddress, tracker.mMessageId, true);
+                                tracker.mDestAddress, tracker.mMessageId, true,
+                                tracker.isSinglePartOrLastPart());
                         break;
                     case ImsSmsImplBase.SEND_STATUS_ERROR:
                         tracker.onFailed(mContext, reason, networkReasonCode);
@@ -247,7 +248,8 @@
                         networkReasonCode,
                         tracker.mMessageId,
                         tracker.isFromDefaultSmsApplication(mContext),
-                        tracker.getInterval());
+                        tracker.getInterval(),
+                        mTelephonyManager.isEmergencyNumber(tracker.mDestAddress));
                 if (mPhone != null) {
                     TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                     if (telephonyAnalytics != null) {
@@ -664,7 +666,8 @@
                     SmsManager.RESULT_SYSTEM_ERROR,
                     tracker.mMessageId,
                     tracker.isFromDefaultSmsApplication(mContext),
-                    tracker.getInterval());
+                    tracker.getInterval(),
+                    mTelephonyManager.isEmergencyNumber(tracker.mDestAddress));
             if (mPhone != null) {
                 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                 if (telephonyAnalytics != null) {
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index eafb4ba..49d1adc 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -74,6 +74,7 @@
 import com.android.internal.telephony.analytics.TelephonyAnalytics;
 import com.android.internal.telephony.analytics.TelephonyAnalytics.SmsMmsAnalytics;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.internal.telephony.satellite.metrics.CarrierRoamingSatelliteSessionStats;
 import com.android.internal.telephony.util.NotificationChannelController;
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.HexDump;
@@ -746,7 +747,9 @@
         // data will be tracked when the message is processed (processMessagePart).
         if (result != Intents.RESULT_SMS_HANDLED && result != Activity.RESULT_OK) {
             mMetrics.writeIncomingSmsError(mPhone.getPhoneId(), is3gpp2(), smsSource, result);
-            mPhone.getSmsStats().onIncomingSmsError(is3gpp2(), smsSource, result);
+            mPhone.getSmsStats().onIncomingSmsError(is3gpp2(), smsSource, result,
+                    TelephonyManager.from(mContext)
+                            .isEmergencyNumber(smsb.getOriginatingAddress()));
             if (mPhone != null) {
                 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                 if (telephonyAnalytics != null) {
@@ -1019,7 +1022,8 @@
                     + (pduList.size() == 0 ? "pduList.size() == 0" : "pduList.contains(null)");
             logeWithLocalLog(errorMsg, tracker.getMessageId());
             mPhone.getSmsStats().onIncomingSmsError(
-                    is3gpp2(), tracker.getSource(), RESULT_SMS_NULL_PDU);
+                    is3gpp2(), tracker.getSource(), RESULT_SMS_NULL_PDU,
+                    TelephonyManager.from(mContext).isEmergencyNumber(tracker.getAddress()));
             if (mPhone != null) {
                 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                 if (telephonyAnalytics != null) {
@@ -1048,7 +1052,9 @@
                                 SmsConstants.FORMAT_3GPP, timestamps, false,
                                 tracker.getMessageId());
                         mPhone.getSmsStats().onIncomingSmsWapPush(tracker.getSource(),
-                                messageCount, RESULT_SMS_NULL_MESSAGE, tracker.getMessageId());
+                                messageCount, RESULT_SMS_NULL_MESSAGE, tracker.getMessageId(),
+                                TelephonyManager.from(mContext)
+                                        .isEmergencyNumber(tracker.getAddress()));
                         return false;
                     }
                 }
@@ -1083,7 +1089,8 @@
             mMetrics.writeIncomingWapPush(mPhone.getPhoneId(), tracker.getSource(),
                     format, timestamps, wapPushResult, tracker.getMessageId());
             mPhone.getSmsStats().onIncomingSmsWapPush(tracker.getSource(), messageCount,
-                    result, tracker.getMessageId());
+                    result, tracker.getMessageId(), TelephonyManager.from(mContext)
+                            .isEmergencyNumber(tracker.getAddress()));
             // result is Activity.RESULT_OK if an ordered broadcast was sent
             if (result == Activity.RESULT_OK) {
                 return true;
@@ -1103,7 +1110,11 @@
         mMetrics.writeIncomingSmsSession(mPhone.getPhoneId(), tracker.getSource(),
                 format, timestamps, block, tracker.getMessageId());
         mPhone.getSmsStats().onIncomingSmsSuccess(is3gpp2(), tracker.getSource(),
-                messageCount, block, tracker.getMessageId());
+                messageCount, block, tracker.getMessageId(),
+                TelephonyManager.from(mContext).isEmergencyNumber(tracker.getAddress()));
+        CarrierRoamingSatelliteSessionStats sessionStats =
+                CarrierRoamingSatelliteSessionStats.getInstance(mPhone.getSubId());
+        sessionStats.onIncomingSms(mPhone.getSubId());
         if (mPhone != null) {
             TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
             if (telephonyAnalytics != null) {
diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index aaeba23..a14ae89 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -17,6 +17,8 @@
 package com.android.internal.telephony;
 
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING;
+import static android.telephony.SubscriptionManager.TRANSFER_STATUS_CONVERTED;
 import static android.telephony.TelephonyManager.ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED;
 import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE;
 import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL;
@@ -54,6 +56,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.util.ArrayUtils;
@@ -157,6 +160,11 @@
     /** The number of active modem count. */
     private int mActiveModemCount;
 
+    private boolean mNeedSetDefaultVoice;
+    private boolean mNeedSetDefaultSms;
+    private boolean mNeedSetDefaultData;
+    private int mConvertedPsimSubId;
+
     private static final String SETTING_USER_PREF_DATA_SUB = "user_preferred_data_sub";
 
     private static class DataSettingsControllerCallback extends DataSettingsManagerCallback {
@@ -242,22 +250,24 @@
         ccm.registerCarrierConfigChangeListener(this::post,
                 (slotIndex, subId, carrierId, specificCarrierId) ->
                         onCarrierConfigChanged(slotIndex, subId));
+
+        mConvertedPsimSubId = getConvertedPsimSubscriptionId();
     }
 
     private boolean hasCalling() {
-        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        if (!TelephonyCapabilities.minimalTelephonyCdmCheck(mFeatureFlags)) return true;
         return mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_TELEPHONY_CALLING);
     }
 
     private boolean hasData() {
-        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        if (!TelephonyCapabilities.minimalTelephonyCdmCheck(mFeatureFlags)) return true;
         return mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_TELEPHONY_DATA);
     }
 
     private boolean hasMessaging() {
-        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        if (!TelephonyCapabilities.minimalTelephonyCdmCheck(mFeatureFlags)) return true;
         return mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_TELEPHONY_MESSAGING);
     }
@@ -402,6 +412,7 @@
         if (DBG) log("onAllSubscriptionsLoaded: mSubInfoInitialized=" + mSubInfoInitialized);
         if (!mSubInfoInitialized) {
             mSubInfoInitialized = true;
+            mConvertedPsimSubId = getConvertedPsimSubscriptionId();
             for (Phone phone : PhoneFactory.getPhones()) {
                 phone.mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
             }
@@ -443,12 +454,13 @@
             return;
         }
 
-        CarrierConfigManager cm = mContext.getSystemService(CarrierConfigManager.class);
-        if (cm != null) {
-            if (CarrierConfigManager.isConfigForIdentifiedCarrier(cm.getConfigForSubId(subId))) {
-                mCarrierConfigLoadedSubIds[phoneId] = subId;
-                reEvaluateAll();
-            }
+        CarrierConfigManager cm;
+        if (!SubscriptionManager.isValidSubscriptionId(subId) // record SIM absent.
+                || ((cm = mContext.getSystemService(CarrierConfigManager.class)) != null
+                && CarrierConfigManager.isConfigForIdentifiedCarrier(
+                        cm.getConfigForSubId(subId)))) {
+            mCarrierConfigLoadedSubIds[phoneId] = subId;
+            reEvaluateAll();
         }
     }
 
@@ -495,11 +507,20 @@
      */
     private boolean isReadyToReevaluate() {
         boolean carrierConfigsLoaded = isCarrierConfigLoadedForAllSub();
+        SatelliteController satelliteController = SatelliteController.getInstance();
+        boolean isSatelliteEnabledOrBeingEnabled = false;
+        if (satelliteController != null) {
+            isSatelliteEnabledOrBeingEnabled = satelliteController.isSatelliteEnabled()
+                    || satelliteController.isSatelliteBeingEnabled();
+        }
+
         if (DBG) {
             log("isReadyToReevaluate: subInfoInitialized=" + mSubInfoInitialized
-                    + ", carrierConfigsLoaded=" + carrierConfigsLoaded);
+                    + ", carrierConfigsLoaded=" + carrierConfigsLoaded
+                    + ", satelliteEnabledOrBeingEnabled=" + isSatelliteEnabledOrBeingEnabled);
         }
-        return mSubInfoInitialized && carrierConfigsLoaded;
+        return mSubInfoInitialized && carrierConfigsLoaded
+                && !isSatelliteEnabledOrBeingEnabled;
     }
 
     private void reEvaluateAll() {
@@ -597,7 +618,6 @@
      */
     protected void updateDefaults() {
         if (DBG) log("updateDefaults");
-
         if (!isReadyToReevaluate()) return;
 
         List<SubscriptionInfo> activeSubInfos = mSubscriptionManagerService
@@ -625,14 +645,19 @@
         // Otherwise, if user just inserted their first SIM, or there's one primary and one
         // opportunistic subscription active (activeSubInfos.size() > 1), we automatically
         // set the primary to be default SIM and return.
-        if (mPrimarySubList.size() == 1 && (change != PRIMARY_SUB_REMOVED
-                || mActiveModemCount == 1)) {
+        boolean conditionForOnePrimarySim =
+                mFeatureFlags.resetPrimarySimDefaultValues() ? mPrimarySubList.size() == 1
+                        : mPrimarySubList.size() == 1
+                        && (change != PRIMARY_SUB_REMOVED || mActiveModemCount == 1);
+        if (conditionForOnePrimarySim) {
             int subId = mPrimarySubList.get(0);
             if (DBG) log("updateDefaultValues: to only primary sub " + subId);
             if (hasData()) mSubscriptionManagerService.setDefaultDataSubId(subId);
             if (hasCalling()) mSubscriptionManagerService.setDefaultVoiceSubId(subId);
             if (hasMessaging()) mSubscriptionManagerService.setDefaultSmsSubId(subId);
-            sendDefaultSubConfirmedNotification(subId);
+            if (!mSubscriptionManagerService.isEsimBootStrapProvisioningActivated()) {
+                sendDefaultSubConfirmedNotification(subId);
+            }
             return;
         }
 
@@ -674,7 +699,12 @@
         // preference auto selection logic or display notification for end used to
         // select voice/data/SMS preferences.
         if (!autoFallbackEnabled) {
-            sendSubChangeNotificationIfNeeded(change, dataSelected, voiceSelected, smsSelected);
+            // Hide the dialog for preferred SIM/data pick if the primary subscription change is
+            // due to the pSIM conversion.
+            if (!setDefaultForPsimConversionChanged(change, dataSelected, voiceSelected,
+                    smsSelected)) {
+                sendSubChangeNotificationIfNeeded(change, dataSelected, voiceSelected, smsSelected);
+            }
         } else {
             updateUserPreferences(mPrimarySubList, dataSelected, voiceSelected, smsSelected);
         }
@@ -685,7 +715,9 @@
         // Update mPrimarySubList. Opportunistic subscriptions can't be default
         // data / voice / sms subscription.
         List<Integer> prevPrimarySubList = mPrimarySubList;
-        mPrimarySubList = activeSubList.stream().filter(info -> !info.isOpportunistic())
+        mPrimarySubList = activeSubList.stream()
+                .filter(info -> !info.isOpportunistic())
+                .filter(info -> info.getProfileClass() != PROFILE_CLASS_PROVISIONING)
                 .map(info -> info.getSubscriptionId())
                 .collect(Collectors.toList());
 
@@ -751,6 +783,12 @@
 
     private void sendSubChangeNotificationIfNeeded(int change, boolean dataSelected,
             boolean voiceSelected, boolean smsSelected) {
+
+        if (mSubscriptionManagerService.isEsimBootStrapProvisioningActivated()) {
+            log("esim bootstrap activation in progress, skip notification");
+            return;
+        }
+
         @TelephonyManager.DefaultSubscriptionSelectType
         int simSelectDialogType = getSimSelectDialogType(
                 change, dataSelected, voiceSelected, smsSelected);
@@ -782,6 +820,119 @@
         }
     }
 
+    /**
+     * Check that the primary subscription has changed due to the pSIM conversion.
+     * @param change Whether to update the mPrimarySubList.
+     * @param dataSelected Whether the default data subscription is updated
+     * @param voiceSelected Whether the default voice subscription is updated
+     * @param smsSelected Whether the default sms subscription is updated
+     * @return {@code true} if the primary subscription has changed due to the pSIM conversion,
+     * {@code false} otherwise.
+     */
+    private boolean setDefaultForPsimConversionChanged(int change, boolean dataSelected,
+            boolean voiceSelected, boolean smsSelected) {
+        if (!mFeatureFlags.supportPsimToEsimConversion()) {
+            log("pSIM to eSIM conversion is not supported");
+            return false;
+        }
+        if (mSubscriptionManagerService.isEsimBootStrapProvisioningActivated()) {
+            log("esim bootstrap activation in progress, skip notification");
+            return false;
+        }
+
+        @TelephonyManager.DefaultSubscriptionSelectType
+        int simSelectDialogType = getSimSelectDialogType(
+                change, dataSelected, voiceSelected, smsSelected);
+        SimCombinationWarningParams simCombinationParams = getSimCombinationWarningParams(change);
+        log("[setDefaultForPsimConversionChanged]showing dialog type:" + simSelectDialogType);
+        if (simSelectDialogType != EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE
+                || simCombinationParams.mWarningType != EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE) {
+            log("[setDefaultForPsimConversionChanged]Converted pSIM:" + mConvertedPsimSubId);
+            int subId = getConvertedPsimSubscriptionId();
+            if (subId != INVALID_SUBSCRIPTION_ID && subId != mConvertedPsimSubId) {
+                // If a primary subscription is removed and only one is left active, ask user
+                // for preferred sub selection if any default setting is not set.
+                if (simSelectDialogType == EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL) {
+                    // check if pSIM's preference is voice.
+                    if (mSubscriptionManagerService.getDefaultVoiceSubId()
+                            == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                        mNeedSetDefaultVoice = true;
+                    }
+                    // check if pSIM's preference is sms.
+                    if (mSubscriptionManagerService.getDefaultSmsSubId()
+                            == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                        mNeedSetDefaultSms = true;
+                    }
+                    // check if pSIM's preference is data.
+                    if (mSubscriptionManagerService.getDefaultDataSubId()
+                            == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                        mNeedSetDefaultData = true;
+                    }
+                    log("select type all, set preferred SIM :" + mPrimarySubList.get(0));
+                    mSubscriptionManagerService.setDefaultVoiceSubId(mPrimarySubList.get(0));
+                    mSubscriptionManagerService.setDefaultSmsSubId(mPrimarySubList.get(0));
+                    mSubscriptionManagerService.setDefaultDataSubId(mPrimarySubList.get(0));
+                    return true;
+                } else if (simSelectDialogType == EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA) {
+                    // If another primary subscription is added or default data is not selected, ask
+                    // user to select default for data as it's most important.
+                    int newSubId = mPrimarySubList.get(0);
+                    log("need to set voice:" + mNeedSetDefaultVoice
+                            + ", sms:" + mNeedSetDefaultSms
+                            + ", data:" + mNeedSetDefaultData);
+                    // if the converted pSIM's preference is voice, set the default
+                    // setting for the changed primary subscription to voice.
+                    if (mNeedSetDefaultVoice) {
+                        log("set preferred call, subId:" + newSubId);
+                        mSubscriptionManagerService.setDefaultVoiceSubId(newSubId);
+                        mNeedSetDefaultVoice = false;
+                    }
+                    // if the converted pSIM's preference is sms, set the default
+                    // setting for the changed primary subscription to sms.
+                    if (mNeedSetDefaultSms) {
+                        log("set preferred sms, subId:" + newSubId);
+                        mSubscriptionManagerService.setDefaultSmsSubId(newSubId);
+                        mNeedSetDefaultSms = false;
+                    }
+                    // if the converted pSIM's preference is data, set the default
+                    // setting for the changed primary subscription to data.
+                    if (mNeedSetDefaultData) {
+                        log("set preferred data, subId:" + newSubId);
+                        mSubscriptionManagerService.setDefaultDataSubId(newSubId);
+                        mNeedSetDefaultData = false;
+                    }
+                    mConvertedPsimSubId = subId;
+                    log("set converted pSIM subId:" + mConvertedPsimSubId);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private int getConvertedPsimSubscriptionId() {
+        // Check to see if any subscription has been converted due to the pSIM conversion.
+        // When the primary subscription is changed, if it is the same subscription as
+        // the previously converted subscription, it is not due to the pSIM conversion.
+        // So the dialog for preferred SIM/data pick should show.
+        // TODO(b/332261793): On Android W, we need to add CONVERTING status.
+        //  The CONVERTING status allows us to determine if pSIM is in the process of converting,
+        //  so we don't need to check for information about previously converted subscriptions.
+        int convertedSubId = INVALID_SUBSCRIPTION_ID;
+        if (mFeatureFlags.supportPsimToEsimConversion()) {
+            List<SubscriptionInfo> infos =
+                    mSubscriptionManagerService.getAvailableSubscriptionInfoList(
+                            mContext.getOpPackageName(), mContext.getAttributionTag());
+            for (SubscriptionInfo info : infos) {
+                if (!info.isEmbedded() && info.getTransferStatus() == TRANSFER_STATUS_CONVERTED) {
+                    convertedSubId = info.getSubscriptionId();
+                }
+            }
+        }
+        log("getConvertedPsimSubscriptionId: convertedSubId=" + convertedSubId);
+        return convertedSubId;
+    }
+
     private int getSimSelectDialogType(int change, boolean dataSelected,
             boolean voiceSelected, boolean smsSelected) {
         int dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE;
diff --git a/src/java/com/android/internal/telephony/Phone.java b/src/java/com/android/internal/telephony/Phone.java
index c088406..aa62acb 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -150,10 +150,10 @@
  */
 
 public abstract class Phone extends Handler implements PhoneInternalInterface {
-    private static final String LOG_TAG = "Phone";
 
     protected final static Object lockForRadioTechnologyChange = new Object();
 
+    private final String mLogTag;
     protected final int USSD_MAX_QUEUE = 10;
 
     // Key used to read and write the saved network selection numeric value
@@ -597,6 +597,7 @@
                     boolean unitTestMode, int phoneId,
                     TelephonyComponentFactory telephonyComponentFactory,
                     FeatureFlags featureFlags) {
+        mLogTag = "Phone-" + phoneId;
         mPhoneId = phoneId;
         mName = name;
         mNotifier = notifier;
@@ -638,10 +639,10 @@
          */
         mDoesRilSendMultipleCallRing = TelephonyProperties.ril_sends_multiple_call_ring()
                 .orElse(true);
-        Rlog.d(LOG_TAG, "mDoesRilSendMultipleCallRing=" + mDoesRilSendMultipleCallRing);
+        Rlog.d(mLogTag, "mDoesRilSendMultipleCallRing=" + mDoesRilSendMultipleCallRing);
 
         mCallRingDelay = TelephonyProperties.call_ring_delay().orElse(3000);
-        Rlog.d(LOG_TAG, "mCallRingDelay=" + mCallRingDelay);
+        Rlog.d(mLogTag, "mCallRingDelay=" + mCallRingDelay);
 
         // Initialize SMS stats
         mSmsStats = new SmsStats(this);
@@ -779,12 +780,13 @@
             case EVENT_SET_NETWORK_MANUAL_COMPLETE:
             case EVENT_SET_NETWORK_AUTOMATIC_COMPLETE:
                 handleSetSelectNetwork((AsyncResult) msg.obj);
+                onSetNetworkSelectionModeCompleted();
                 return;
         }
 
         switch(msg.what) {
             case EVENT_CALL_RING:
-                Rlog.d(LOG_TAG, "Event EVENT_CALL_RING Received state=" + getState());
+                Rlog.d(mLogTag, "Event EVENT_CALL_RING Received state=" + getState());
                 ar = (AsyncResult)msg.obj;
                 if (ar.exception == null) {
                     PhoneConstants.State state = getState();
@@ -800,7 +802,7 @@
                 break;
 
             case EVENT_CALL_RING_CONTINUE:
-                Rlog.d(LOG_TAG, "Event EVENT_CALL_RING_CONTINUE Received state=" + getState());
+                Rlog.d(mLogTag, "Event EVENT_CALL_RING_CONTINUE Received state=" + getState());
                 if (getState() == PhoneConstants.State.RINGING) {
                     sendIncomingCallRingNotification(msg.arg1);
                 }
@@ -813,7 +815,7 @@
             case EVENT_INITIATE_SILENT_REDIAL:
                 // This is an ImsPhone -> GsmCdmaPhone redial
                 // See ImsPhone#initiateSilentRedial
-                Rlog.d(LOG_TAG, "Event EVENT_INITIATE_SILENT_REDIAL Received");
+                Rlog.d(mLogTag, "Event EVENT_INITIATE_SILENT_REDIAL Received");
                 ar = (AsyncResult) msg.obj;
                 if ((ar.exception == null) && (ar.result != null)) {
                     SilentRedialParam result = (SilentRedialParam) ar.result;
@@ -827,13 +829,13 @@
                         // one with a callback registered to TelephonyConnection. Notify the
                         // redial happened over that Phone so that it can be replaced with the
                         // new GSM/CDMA Connection.
-                        Rlog.d(LOG_TAG, "Notify redial connection changed cn: " + cn);
+                        Rlog.d(mLogTag, "Notify redial connection changed cn: " + cn);
                         if (mImsPhone != null) {
                             // Don't care it is null or not.
                             mImsPhone.notifyRedialConnectionChanged(cn);
                         }
                     } catch (CallStateException e) {
-                        Rlog.e(LOG_TAG, "silent redial failed: " + e);
+                        Rlog.e(mLogTag, "silent redial failed: " + e);
                         if (mImsPhone != null) {
                             mImsPhone.notifyRedialConnectionChanged(null);
                         }
@@ -846,7 +848,7 @@
                 if (ar.exception == null) {
                     handleSrvccStateChanged((int[]) ar.result);
                 } else {
-                    Rlog.e(LOG_TAG, "Srvcc exception: " + ar.exception);
+                    Rlog.e(mLogTag, "Srvcc exception: " + ar.exception);
                 }
                 break;
 
@@ -865,7 +867,7 @@
                     try {
                         mUsageSettingFromModem = ((int[]) ar.result)[0];
                     } catch (NullPointerException | ClassCastException e) {
-                        Rlog.e(LOG_TAG, "Invalid response for usage setting " + ar.result);
+                        Rlog.e(mLogTag, "Invalid response for usage setting " + ar.result);
                         break;
                     }
 
@@ -880,9 +882,9 @@
                         if (ce.getCommandError() == CommandException.Error.REQUEST_NOT_SUPPORTED) {
                             mIsUsageSettingSupported = false;
                         }
-                        Rlog.w(LOG_TAG, "Unexpected failure to retrieve usage setting " + ce);
+                        Rlog.w(mLogTag, "Unexpected failure to retrieve usage setting " + ce);
                     } catch (ClassCastException unused) {
-                        Rlog.e(LOG_TAG, "Invalid Exception for usage setting " + ar.exception);
+                        Rlog.e(mLogTag, "Invalid Exception for usage setting " + ar.exception);
                         break; // technically extraneous, but good hygiene
                     }
                 }
@@ -895,9 +897,9 @@
                         if (ce.getCommandError() == CommandException.Error.REQUEST_NOT_SUPPORTED) {
                             mIsUsageSettingSupported = false;
                         }
-                        Rlog.w(LOG_TAG, "Unexpected failure to set usage setting " + ce);
+                        Rlog.w(mLogTag, "Unexpected failure to set usage setting " + ce);
                     } catch (ClassCastException unused) {
-                        Rlog.e(LOG_TAG, "Invalid Exception for usage setting " + ar.exception);
+                        Rlog.e(mLogTag, "Invalid Exception for usage setting " + ar.exception);
                         break; // technically extraneous, but good hygiene
                     }
                 }
@@ -931,7 +933,7 @@
     }
 
     private void handleSrvccStateChanged(int[] ret) {
-        Rlog.d(LOG_TAG, "handleSrvccStateChanged");
+        Rlog.d(mLogTag, "handleSrvccStateChanged");
 
         ArrayList<Connection> conn = null;
         Phone imsPhone = mImsPhone;
@@ -948,7 +950,7 @@
                         conn = imsPhone.getHandoverConnection();
                         migrateFrom(imsPhone);
                     } else {
-                        Rlog.d(LOG_TAG, "HANDOVER_STARTED: mImsPhone null");
+                        Rlog.d(mLogTag, "HANDOVER_STARTED: mImsPhone null");
                     }
                     break;
                 case TelephonyManager.SRVCC_STATE_HANDOVER_COMPLETED:
@@ -1186,7 +1188,7 @@
                     to.add((Registrant) from.get(i));
                 }
             } else {
-                Rlog.d(LOG_TAG, "msg is null");
+                Rlog.d(mLogTag, "msg is null");
             }
         }
     }
@@ -1483,7 +1485,7 @@
      */
     @UnsupportedAppUsage
     public void setNetworkSelectionModeAutomatic(Message response) {
-        Rlog.d(LOG_TAG, "setNetworkSelectionModeAutomatic, querying current mode");
+        Rlog.d(mLogTag, "setNetworkSelectionModeAutomatic, querying current mode");
         // we don't want to do this unnecessarily - it actually causes
         // the radio to repeat network selection and is costly
         // first check if we're already in automatic mode
@@ -1518,11 +1520,11 @@
         nsm.operatorAlphaShort = "";
 
         if (doAutomatic) {
-            Rlog.d(LOG_TAG, "setNetworkSelectionModeAutomatic - set network selection auto");
+            Rlog.d(mLogTag, "setNetworkSelectionModeAutomatic - set network selection auto");
             Message msg = obtainMessage(EVENT_SET_NETWORK_AUTOMATIC_COMPLETE, nsm);
             mCi.setNetworkSelectionModeAutomatic(msg);
         } else {
-            Rlog.d(LOG_TAG, "setNetworkSelectionModeAutomatic - already auto, ignoring");
+            Rlog.d(mLogTag, "setNetworkSelectionModeAutomatic - already auto, ignoring");
             // let the calling application know that the we are ignoring automatic mode switch.
             if (nsm.message != null) {
                 nsm.message.arg1 = ALREADY_IN_AUTO_SELECTION;
@@ -1536,6 +1538,12 @@
     }
 
     /**
+     * Called when setting network selection mode is complete.
+     */
+    protected void onSetNetworkSelectionModeCompleted() {
+    }
+
+    /**
      * Query the radio for the current network selection mode.
      *
      * Return values:
@@ -1610,10 +1618,10 @@
 
             // commit and log the result.
             if (!editor.commit()) {
-                Rlog.e(LOG_TAG, "failed to commit network selection preference");
+                Rlog.e(mLogTag, "failed to commit network selection preference");
             }
         } else {
-            Rlog.e(LOG_TAG, "Cannot update network selection preference due to invalid subId " +
+            Rlog.e(mLogTag, "Cannot update network selection preference due to invalid subId " +
                     subId);
         }
     }
@@ -1624,7 +1632,7 @@
      * @param nsm PLMN info of the selected network
      */
     protected void updateManualNetworkSelection(NetworkSelectMessage nsm)  {
-        Rlog.e(LOG_TAG, "updateManualNetworkSelection() should be overridden");
+        Rlog.e(mLogTag, "updateManualNetworkSelection() should be overridden");
     }
 
     /**
@@ -1634,7 +1642,7 @@
         // look for our wrapper within the asyncresult, skip the rest if it
         // is null.
         if (!(ar.userObj instanceof NetworkSelectMessage)) {
-            Rlog.e(LOG_TAG, "unexpected result from user object.");
+            Rlog.e(mLogTag, "unexpected result from user object.");
             return;
         }
 
@@ -1698,12 +1706,12 @@
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
         SharedPreferences.Editor editor = sp.edit();
         editor.putInt(CLIR_KEY + getSubId(), commandInterfaceCLIRMode);
-        Rlog.i(LOG_TAG, "saveClirSetting: " + CLIR_KEY + getSubId() + "="
+        Rlog.i(mLogTag, "saveClirSetting: " + CLIR_KEY + getSubId() + "="
                 + commandInterfaceCLIRMode);
 
         // Commit and log the result.
         if (!editor.commit()) {
-            Rlog.e(LOG_TAG, "Failed to commit CLIR preference");
+            Rlog.e(mLogTag, "Failed to commit CLIR preference");
         }
     }
 
@@ -1925,13 +1933,13 @@
         IccFileHandler fh;
 
         if (uiccApplication == null) {
-            Rlog.d(LOG_TAG, "getIccFileHandler: uiccApplication == null, return null");
+            Rlog.d(mLogTag, "getIccFileHandler: uiccApplication == null, return null");
             fh = null;
         } else {
             fh = uiccApplication.getIccFileHandler();
         }
 
-        Rlog.d(LOG_TAG, "getIccFileHandler: fh=" + fh);
+        Rlog.d(mLogTag, "getIccFileHandler: fh=" + fh);
         return fh;
     }
 
@@ -2018,7 +2026,7 @@
      * Retrieves the SignalStrengthController of the phone instance.
      */
     public SignalStrengthController getSignalStrengthController() {
-        Log.wtf(LOG_TAG, "getSignalStrengthController return null.");
+        Log.wtf(mLogTag, "getSignalStrengthController return null.");
         return null;
     }
 
@@ -2052,7 +2060,7 @@
      * Update voice mail count related fields and notify listeners
      */
     public void updateVoiceMail() {
-        Rlog.e(LOG_TAG, "updateVoiceMail() should be overridden");
+        Rlog.e(mLogTag, "updateVoiceMail() should be overridden");
     }
 
     public AppType getCurrentUiccAppType() {
@@ -2178,7 +2186,7 @@
         if (SubscriptionManager.isValidSubscriptionId(subId)) {
             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
             status = sp.getInt(CF_STATUS + subId, IccRecords.CALL_FORWARDING_STATUS_UNKNOWN);
-            Rlog.d(LOG_TAG, "getCallForwardingIndicatorFromSharedPref: for subId " + subId + "= " +
+            Rlog.d(mLogTag, "getCallForwardingIndicatorFromSharedPref: for subId " + subId + "= " +
                     status);
             // Check for old preference if status is UNKNOWN for current subId. This part of the
             // code is needed only when upgrading from M to N.
@@ -2192,9 +2200,9 @@
                         status = sp.getInt(CF_STATUS, IccRecords.CALL_FORWARDING_STATUS_DISABLED);
                         setCallForwardingIndicatorInSharedPref(
                                 status == IccRecords.CALL_FORWARDING_STATUS_ENABLED ? true : false);
-                        Rlog.d(LOG_TAG, "getCallForwardingIndicatorFromSharedPref: " + status);
+                        Rlog.d(mLogTag, "getCallForwardingIndicatorFromSharedPref: " + status);
                     } else {
-                        Rlog.d(LOG_TAG, "getCallForwardingIndicatorFromSharedPref: returning " +
+                        Rlog.d(mLogTag, "getCallForwardingIndicatorFromSharedPref: returning " +
                                 "DISABLED as status for matching subscriberId not found");
                     }
 
@@ -2206,7 +2214,7 @@
                 }
             }
         } else {
-            Rlog.e(LOG_TAG, "getCallForwardingIndicatorFromSharedPref: invalid subId " + subId);
+            Rlog.e(mLogTag, "getCallForwardingIndicatorFromSharedPref: invalid subId " + subId);
         }
         return status;
     }
@@ -2215,7 +2223,7 @@
         int status = enable ? IccRecords.CALL_FORWARDING_STATUS_ENABLED :
                 IccRecords.CALL_FORWARDING_STATUS_DISABLED;
         int subId = getSubId();
-        Rlog.i(LOG_TAG, "setCallForwardingIndicatorInSharedPref: Storing status = " + status +
+        Rlog.i(mLogTag, "setCallForwardingIndicatorInSharedPref: Storing status = " + status +
                 " in pref " + CF_STATUS + subId);
 
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
@@ -2258,7 +2266,7 @@
      */
     public boolean getCallForwardingIndicator() {
         if (getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
-            Rlog.e(LOG_TAG, "getCallForwardingIndicator: not possible in CDMA");
+            Rlog.e(mLogTag, "getCallForwardingIndicator: not possible in CDMA");
             return false;
         }
         IccRecords r = getIccRecords();
@@ -2269,7 +2277,7 @@
         if (callForwardingIndicator == IccRecords.CALL_FORWARDING_STATUS_UNKNOWN) {
             callForwardingIndicator = getCallForwardingIndicatorFromSharedPref();
         }
-        Rlog.v(LOG_TAG, "getCallForwardingIndicator: iccForwardingFlag=" + (r != null
+        Rlog.v(mLogTag, "getCallForwardingIndicator: iccForwardingFlag=" + (r != null
                     ? r.getVoiceCallForwardingFlag() : "null") + ", sharedPrefFlag="
                     + getCallForwardingIndicatorFromSharedPref());
         return (callForwardingIndicator == IccRecords.CALL_FORWARDING_STATUS_ENABLED);
@@ -2466,7 +2474,7 @@
             for (String pair : result.trim().split(",")) {
                 String[] networkTypesValues = (pair.trim().toLowerCase(Locale.ROOT)).split("=");
                 if (networkTypesValues.length != 2) {
-                    Rlog.e(LOG_TAG, "Invalid ALLOWED_NETWORK_TYPES from DB, value = " + pair);
+                    Rlog.e(mLogTag, "Invalid ALLOWED_NETWORK_TYPES from DB, value = " + pair);
                     continue;
                 }
                 int key = convertAllowedNetworkTypeDbNameToMapIndex(networkTypesValues[0]);
@@ -2486,7 +2494,7 @@
                 }
             }
         } catch (NumberFormatException e) {
-            Rlog.e(LOG_TAG, "allowedNetworkTypes NumberFormat exception" + e);
+            Rlog.e(mLogTag, "allowedNetworkTypes NumberFormat exception" + e);
         }
 
         for (int key : oldAllowedNetworkTypes.keySet()) {
@@ -2605,7 +2613,7 @@
     protected void updateAllowedNetworkTypes(Message response) {
         int modemRaf = getRadioAccessFamily();
         if (modemRaf == RadioAccessFamily.RAF_UNKNOWN) {
-            Rlog.d(LOG_TAG, "setPreferredNetworkType: Abort, unknown RAF: "
+            Rlog.d(mLogTag, "setPreferredNetworkType: Abort, unknown RAF: "
                     + modemRaf);
             if (response != null) {
                 CommandException ex;
@@ -2714,7 +2722,7 @@
      * @param onComplete a callback message when the action is completed
      */
     public void setUiTTYMode(int uiTtyMode, Message onComplete) {
-        Rlog.d(LOG_TAG, "unexpected setUiTTYMode method call");
+        Rlog.d(mLogTag, "unexpected setUiTTYMode method call");
     }
 
     /**
@@ -2841,7 +2849,7 @@
     public boolean eraseDataInSharedPreferences() {
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
         SharedPreferences.Editor editor = sp.edit();
-        Rlog.d(LOG_TAG, "Erase all data saved in SharedPreferences");
+        Rlog.d(mLogTag, "Erase all data saved in SharedPreferences");
         editor.clear();
         return editor.commit();
     }
@@ -3087,7 +3095,7 @@
                     isVideoCallOrConference(mImsPhone.getBackgroundCall()) ||
                     isVideoCallOrConference(mImsPhone.getRingingCall());
         }
-        Rlog.d(LOG_TAG, "isImsVideoCallOrConferencePresent: " + isPresent);
+        Rlog.d(mLogTag, "isImsVideoCallOrConferencePresent: " + isPresent);
         return isPresent;
     }
 
@@ -3112,7 +3120,7 @@
         int subId = getSubId();
         if (SubscriptionManager.isValidSubscriptionId(subId)) {
 
-            Rlog.d(LOG_TAG, "setVoiceMessageCount: Storing Voice Mail Count = " + countWaiting +
+            Rlog.d(mLogTag, "setVoiceMessageCount: Storing Voice Mail Count = " + countWaiting +
                     " for mVmCountKey = " + VM_COUNT + subId + " in preferences.");
 
             SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
@@ -3120,16 +3128,16 @@
             editor.putInt(VM_COUNT + subId, countWaiting);
             editor.apply();
         } else {
-            Rlog.e(LOG_TAG, "setVoiceMessageCount in sharedPreference: invalid subId " + subId);
+            Rlog.e(mLogTag, "setVoiceMessageCount in sharedPreference: invalid subId " + subId);
         }
         // store voice mail count in SIM
         IccRecords records = UiccController.getInstance().getIccRecords(
                 mPhoneId, UiccController.APP_FAM_3GPP);
         if (records != null) {
-            Rlog.d(LOG_TAG, "setVoiceMessageCount: updating SIM Records");
+            Rlog.d(mLogTag, "setVoiceMessageCount: updating SIM Records");
             records.setVoiceMessageWaiting(1, countWaiting);
         } else {
-            Rlog.d(LOG_TAG, "setVoiceMessageCount: SIM Records not found");
+            Rlog.d(mLogTag, "setVoiceMessageCount: SIM Records not found");
         }
         // notify listeners of voice mail
         notifyMessageWaitingIndicator();
@@ -3145,7 +3153,7 @@
             int countFromSP = sp.getInt(VM_COUNT + subId, invalidCount);
             if (countFromSP != invalidCount) {
                 countVoiceMessages = countFromSP;
-                Rlog.d(LOG_TAG, "getStoredVoiceMessageCount: from preference for subId " + subId +
+                Rlog.d(mLogTag, "getStoredVoiceMessageCount: from preference for subId " + subId +
                         "= " + countVoiceMessages);
             } else {
                 // Check for old preference if count not found for current subId. This part of the
@@ -3158,10 +3166,10 @@
                         // get voice mail count from preferences
                         countVoiceMessages = sp.getInt(VM_COUNT, 0);
                         setVoiceMessageCount(countVoiceMessages);
-                        Rlog.d(LOG_TAG, "getStoredVoiceMessageCount: from preference = " +
+                        Rlog.d(mLogTag, "getStoredVoiceMessageCount: from preference = " +
                                 countVoiceMessages);
                     } else {
-                        Rlog.d(LOG_TAG, "getStoredVoiceMessageCount: returning 0 as count for " +
+                        Rlog.d(mLogTag, "getStoredVoiceMessageCount: returning 0 as count for " +
                                 "matching subscriberId not found");
 
                     }
@@ -3173,7 +3181,7 @@
                 }
             }
         } else {
-            Rlog.e(LOG_TAG, "getStoredVoiceMessageCount: invalid subId " + subId);
+            Rlog.e(mLogTag, "getStoredVoiceMessageCount: invalid subId " + subId);
         }
         return countVoiceMessages;
     }
@@ -3706,17 +3714,17 @@
                             }
                         }
                     } catch (NumberFormatException e) {
-                        Rlog.e(LOG_TAG, "Exception in getProvisioningUrlBaseFromFile: " + e);
+                        Rlog.e(mLogTag, "Exception in getProvisioningUrlBaseFromFile: " + e);
                     }
                 }
             }
             return null;
         } catch (FileNotFoundException e) {
-            Rlog.e(LOG_TAG, "Carrier Provisioning Urls file not found");
+            Rlog.e(mLogTag, "Carrier Provisioning Urls file not found");
         } catch (XmlPullParserException e) {
-            Rlog.e(LOG_TAG, "Xml parser exception reading Carrier Provisioning Urls file: " + e);
+            Rlog.e(mLogTag, "Xml parser exception reading Carrier Provisioning Urls file: " + e);
         } catch (IOException e) {
-            Rlog.e(LOG_TAG, "I/O exception reading Carrier Provisioning Urls file: " + e);
+            Rlog.e(mLogTag, "I/O exception reading Carrier Provisioning Urls file: " + e);
         }
         return null;
     }
@@ -3728,9 +3736,9 @@
         String url = getProvisioningUrlBaseFromFile();
         if (TextUtils.isEmpty(url)) {
             url = mContext.getResources().getString(R.string.mobile_provisioning_url);
-            Rlog.d(LOG_TAG, "getMobileProvisioningUrl: url from resource =" + url);
+            Rlog.d(mLogTag, "getMobileProvisioningUrl: url from resource =" + url);
         } else {
-            Rlog.d(LOG_TAG, "getMobileProvisioningUrl: url from File =" + url);
+            Rlog.d(mLogTag, "getMobileProvisioningUrl: url from File =" + url);
         }
         // Populate the iccid, imei and phone number in the provisioning url.
         if (!TextUtils.isEmpty(url)) {
@@ -3804,7 +3812,7 @@
      * version scoped to their packages
      */
     public void notifyNewRingingConnectionP(Connection cn) {
-        Rlog.i(LOG_TAG, String.format(
+        Rlog.i(mLogTag, String.format(
                 "notifyNewRingingConnection: phoneId=[%d], connection=[%s], registrants=[%s]",
                 getPhoneId(), cn, getNewRingingConnectionRegistrantsAsString()));
         if (!mIsVoiceCapable)
@@ -3860,12 +3868,12 @@
     private void sendIncomingCallRingNotification(int token) {
         if (mIsVoiceCapable && !mDoesRilSendMultipleCallRing &&
                 (token == mCallRingContinueToken)) {
-            Rlog.d(LOG_TAG, "Sending notifyIncomingRing");
+            Rlog.d(mLogTag, "Sending notifyIncomingRing");
             notifyIncomingRing();
             sendMessageDelayed(
                     obtainMessage(EVENT_CALL_RING_CONTINUE, token, 0), mCallRingDelay);
         } else {
-            Rlog.d(LOG_TAG, "Ignoring ring notification request,"
+            Rlog.d(mLogTag, "Ignoring ring notification request,"
                     + " mDoesRilSendMultipleCallRing=" + mDoesRilSendMultipleCallRing
                     + " token=" + token
                     + " mCallRingContinueToken=" + mCallRingContinueToken
@@ -3905,7 +3913,7 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public IsimRecords getIsimRecords() {
-        Rlog.e(LOG_TAG, "getIsimRecords() is only supported on LTE devices");
+        Rlog.e(mLogTag, "getIsimRecords() is only supported on LTE devices");
         return null;
     }
 
@@ -3938,7 +3946,7 @@
      */
     public void setVoiceMessageWaiting(int line, int countWaiting) {
         // This function should be overridden by class GsmCdmaPhone.
-        Rlog.e(LOG_TAG, "Error! This function should never be executed, inactive Phone.");
+        Rlog.e(mLogTag, "Error! This function should never be executed, inactive Phone.");
     }
 
     /**
@@ -4018,6 +4026,15 @@
         return;
     }
 
+    /**
+     * Deletes all the keys for a given Carrier from the device keystore.
+     * @param carrierId : the carrier ID which needs to be matched in the delete query
+     * @param simOperator : MccMnc which needs to be matched in the delete query.
+     */
+    public void deleteCarrierInfoForImsiEncryption(int carrierId, String simOperator) {
+
+    }
+
     public int getCarrierId() {
         return TelephonyManager.UNKNOWN_CARRIER_ID;
     }
@@ -4188,7 +4205,7 @@
                 isImsRegistered = sst.isImsRegistered();
             }
         }
-        Rlog.d(LOG_TAG, "isImsRegistered =" + isImsRegistered);
+        Rlog.d(mLogTag, "isImsRegistered =" + isImsRegistered);
         return isImsRegistered;
     }
 
@@ -4202,7 +4219,7 @@
         if (imsPhone != null) {
             isWifiCallingEnabled = imsPhone.isWifiCallingEnabled();
         }
-        Rlog.d(LOG_TAG, "isWifiCallingEnabled =" + isWifiCallingEnabled);
+        Rlog.d(mLogTag, "isWifiCallingEnabled =" + isWifiCallingEnabled);
         return isWifiCallingEnabled;
     }
 
@@ -4216,7 +4233,7 @@
         if (imsPhone != null) {
             isAvailable = imsPhone.isImsCapabilityAvailable(capability, regTech);
         }
-        Rlog.d(LOG_TAG, "isImsCapabilityAvailable, capability=" + capability + ", regTech="
+        Rlog.d(mLogTag, "isImsCapabilityAvailable, capability=" + capability + ", regTech="
                 + regTech + ", isAvailable=" + isAvailable);
         return isAvailable;
     }
@@ -4240,7 +4257,7 @@
         if (imsPhone != null) {
             isVolteEnabled = imsPhone.isVoiceOverCellularImsEnabled();
         }
-        Rlog.d(LOG_TAG, "isVoiceOverCellularImsEnabled=" + isVolteEnabled);
+        Rlog.d(mLogTag, "isVoiceOverCellularImsEnabled=" + isVolteEnabled);
         return isVolteEnabled;
     }
 
@@ -4254,7 +4271,7 @@
         if (imsPhone != null) {
             regTech = imsPhone.getImsRegistrationTech();
         }
-        Rlog.d(LOG_TAG, "getImsRegistrationTechnology =" + regTech);
+        Rlog.d(mLogTag, "getImsRegistrationTechnology =" + regTech);
         return regTech;
     }
 
@@ -4836,7 +4853,7 @@
     public boolean isDeviceIdle() {
         DeviceStateMonitor dsm = getDeviceStateMonitor();
         if (dsm == null) {
-            Rlog.e(LOG_TAG, "isDeviceIdle: DeviceStateMonitor is null");
+            Rlog.e(mLogTag, "isDeviceIdle: DeviceStateMonitor is null");
             return false;
         }
         return !dsm.shouldEnableHighPowerConsumptionIndications();
@@ -4850,7 +4867,7 @@
     public void notifyDeviceIdleStateChanged(boolean isIdle) {
         SignalStrengthController ssc = getSignalStrengthController();
         if (ssc == null) {
-            Rlog.e(LOG_TAG, "notifyDeviceIdleStateChanged: SignalStrengthController is null");
+            Rlog.e(mLogTag, "notifyDeviceIdleStateChanged: SignalStrengthController is null");
             return;
         }
         ssc.onDeviceIdleStateChanged(isIdle);
@@ -4899,15 +4916,15 @@
     /**
      * @return The data network controller
      */
-    public @Nullable DataNetworkController getDataNetworkController() {
+    public @NonNull DataNetworkController getDataNetworkController() {
         return mDataNetworkController;
     }
 
     /**
      * @return The data settings manager
      */
-    public @Nullable DataSettingsManager getDataSettingsManager() {
-        if (mDataNetworkController == null) return null;
+    @NonNull
+    public DataSettingsManager getDataSettingsManager() {
         return mDataNetworkController.getDataSettingsManager();
     }
 
@@ -5282,7 +5299,7 @@
      * @param type for callback mode entry.
      */
     public void startCallbackMode(@TelephonyManager.EmergencyCallbackModeType int type) {
-        Rlog.d(LOG_TAG, "startCallbackMode:type=" + type);
+        Rlog.d(mLogTag, "startCallbackMode:type=" + type);
         mNotifier.notifyCallbackModeStarted(this, type);
     }
 
@@ -5293,10 +5310,22 @@
      */
     public void stopCallbackMode(@TelephonyManager.EmergencyCallbackModeType int type,
             @TelephonyManager.EmergencyCallbackModeStopReason int reason) {
-        Rlog.d(LOG_TAG, "stopCallbackMode:type=" + type + ", reason=" + reason);
+        Rlog.d(mLogTag, "stopCallbackMode:type=" + type + ", reason=" + reason);
         mNotifier.notifyCallbackModeStopped(this, type, reason);
     }
 
+    /**
+     * Notify carrier roaming non-terrestrial network mode changed
+     * @param active {@code true} If the device is connected to carrier roaming
+     *                           non-terrestrial network or was connected within the
+     *                           {CarrierConfigManager#KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT}
+     *                           duration, {code false} otherwise.
+     */
+    public void notifyCarrierRoamingNtnModeChanged(boolean active) {
+        logd("notifyCarrierRoamingNtnModeChanged active:" + active);
+        mNotifier.notifyCarrierRoamingNtnModeChanged(this, active);
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Phone: subId=" + getSubId());
         pw.println(" mPhoneId=" + mPhoneId);
@@ -5319,7 +5348,7 @@
         pw.println(" isDnsCheckDisabled()=" + isDnsCheckDisabled());
         pw.println(" getUnitTestMode()=" + getUnitTestMode());
         pw.println(" getState()=" + getState());
-        pw.println(" getIccSerialNumber()=" + Rlog.pii(LOG_TAG, getIccSerialNumber()));
+        pw.println(" getIccSerialNumber()=" + Rlog.pii(mLogTag, getIccSerialNumber()));
         pw.println(" getIccRecordsLoaded()=" + getIccRecordsLoaded());
         pw.println(" getMessageWaitingIndicator()=" + getMessageWaitingIndicator());
         pw.println(" getCallForwardingIndicator()=" + getCallForwardingIndicator());
@@ -5504,18 +5533,14 @@
     }
 
     private void logd(String s) {
-        Rlog.d(LOG_TAG, "[" + mPhoneId + "] " + s);
+        Rlog.d(mLogTag, "[" + mPhoneId + "] " + s);
     }
 
     private void logi(String s) {
-        Rlog.i(LOG_TAG, "[" + mPhoneId + "] " + s);
+        Rlog.i(mLogTag, "[" + mPhoneId + "] " + s);
     }
 
     private void loge(String s) {
-        Rlog.e(LOG_TAG, "[" + mPhoneId + "] " + s);
-    }
-
-    private static String pii(String s) {
-        return Rlog.pii(LOG_TAG, s);
+        Rlog.e(mLogTag, "[" + mPhoneId + "] " + s);
     }
 }
diff --git a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
index ef96d89..ffa5b69 100644
--- a/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
+++ b/src/java/com/android/internal/telephony/PhoneConfigurationManager.java
@@ -45,6 +45,7 @@
 
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Optional;
@@ -97,7 +98,9 @@
 
     private static PhoneConfigurationManager sInstance = null;
     private final Context mContext;
-    private PhoneCapability mStaticCapability;
+    // Static capability retrieved from the modem - may be null in the case where no info has been
+    // retrieved yet.
+    private PhoneCapability mStaticCapability = null;
     private final Set<Integer> mSlotsSupportingSimultaneousCellularCalls = new HashSet<>(3);
     private final Set<Integer> mSubIdsSupportingSimultaneousCellularCalls = new HashSet<>(3);
     private final HashSet<Consumer<Set<Integer>>> mSimultaneousCellularCallingListeners =
@@ -151,8 +154,6 @@
         mFeatureFlags = featureFlags;
         // TODO: send commands to modem once interface is ready.
         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
-        //initialize with default, it'll get updated when RADIO is ON/AVAILABLE
-        mStaticCapability = getDefaultCapability();
         mRadioConfig = RadioConfig.getInstance();
         mHandler = new ConfigManagerHandler();
         mPhoneStatusMap = new HashMap<>();
@@ -256,7 +257,8 @@
         boolean halSupportSimulCalling = mRadioConfig != null
                 && mRadioConfig.getRadioConfigProxy(null).getVersion().greaterOrEqual(
                         RIL.RADIO_HAL_VERSION_2_2)
-                && getPhoneCount() > 1 && mStaticCapability.getMaxActiveVoiceSubscriptions() > 1;
+                && getPhoneCount() > 1
+                && getCellularStaticPhoneCapability().getMaxActiveVoiceSubscriptions() > 1;
         // Register for simultaneous calling support changes in the modem if the HAL supports it
         if (halSupportSimulCalling) {
             updateSimultaneousCallingSupport();
@@ -318,7 +320,7 @@
                         log("Unable to add phoneStatus to cache. "
                                 + "No phone object provided for event " + msg.what);
                     }
-                    getStaticPhoneCapability();
+                    updateRadioCapability();
                     break;
                 case EVENT_SWITCH_DSDS_CONFIG_DONE:
                     ar = (AsyncResult) msg.obj;
@@ -343,7 +345,7 @@
                 case EVENT_GET_PHONE_CAPABILITY_DONE:
                     ar = (AsyncResult) msg.obj;
                     if (ar != null && ar.exception == null) {
-                        mStaticCapability = (PhoneCapability) ar.result;
+                        setStaticPhoneCapability((PhoneCapability) ar.result);
                         notifyCapabilityChanged();
                         for (Listener l : mListeners) {
                             l.onPhoneCapabilityChanged();
@@ -376,12 +378,12 @@
                     }
                     ar = (AsyncResult) msg.obj;
                     if (ar != null && ar.exception == null) {
-                        int[] returnedIntArray = (int[]) ar.result;
+                        List<Integer> returnedArrayList = (List<Integer>) ar.result;
                         if (!mSlotsSupportingSimultaneousCellularCalls.isEmpty()) {
                             mSlotsSupportingSimultaneousCellularCalls.clear();
                         }
                         int maxValidPhoneSlot = getPhoneCount() - 1;
-                        for (int i : returnedIntArray) {
+                        for (int i : returnedArrayList) {
                             if (i < 0 || i > maxValidPhoneSlot) {
                                 loge("Invalid slot supporting DSDA =" + i + ". Disabling DSDA.");
                                 mSlotsSupportingSimultaneousCellularCalls.clear();
@@ -534,21 +536,43 @@
     }
 
     /**
-     * get static overall phone capabilities for all phones.
+     * @return static overall phone capabilities for all phones, including voice overrides.
      */
     public synchronized PhoneCapability getStaticPhoneCapability() {
-        if (getDefaultCapability().equals(mStaticCapability)) {
-            log("getStaticPhoneCapability: sending the request for getting PhoneCapability");
-            Message callback = Message.obtain(
-                    mHandler, EVENT_GET_PHONE_CAPABILITY_DONE);
-            mRadioConfig.getPhoneCapability(callback);
-        }
-        mStaticCapability = maybeOverrideMaxActiveVoiceSubscriptions(mStaticCapability);
-        log("getStaticPhoneCapability: mStaticCapability " + mStaticCapability);
+        boolean isDefault = mStaticCapability == null;
+        PhoneCapability caps = isDefault ? getDefaultCapability() : mStaticCapability;
+        caps = maybeOverrideMaxActiveVoiceSubscriptions(caps);
+        log("getStaticPhoneCapability: isDefault=" + isDefault + ", caps=" + caps);
+        return caps;
+    }
+
+    /**
+     * @return untouched capabilities returned from the modem
+     */
+    private synchronized PhoneCapability getCellularStaticPhoneCapability() {
+        log("getCellularStaticPhoneCapability: mStaticCapability " + mStaticCapability);
         return mStaticCapability;
     }
 
     /**
+     * Caches the static PhoneCapability returned by the modem
+     */
+    public synchronized void setStaticPhoneCapability(PhoneCapability capability) {
+        log("setStaticPhoneCapability: mStaticCapability " + capability);
+        mStaticCapability = capability;
+    }
+
+    /**
+     * Query the modem to return its static PhoneCapability and cache it
+     */
+    @VisibleForTesting
+    public void updateRadioCapability() {
+        log("updateRadioCapability: sending the request for getting PhoneCapability");
+        Message callback = Message.obtain(mHandler, EVENT_GET_PHONE_CAPABILITY_DONE);
+        mRadioConfig.getPhoneCapability(callback);
+    }
+
+    /**
      * get configuration related status of each phone.
      */
     public PhoneCapability getCurrentPhoneCapability() {
@@ -556,12 +580,11 @@
     }
 
     public int getNumberOfModemsWithSimultaneousDataConnections() {
-        return mStaticCapability.getMaxActiveDataSubscriptions();
+        return getStaticPhoneCapability().getMaxActiveDataSubscriptions();
     }
 
     public int getNumberOfModemsWithSimultaneousVoiceConnections() {
-        return maybeOverrideMaxActiveVoiceSubscriptions(mStaticCapability)
-                .getMaxActiveVoiceSubscriptions();
+        return getStaticPhoneCapability().getMaxActiveVoiceSubscriptions();
     }
 
     public boolean isVirtualDsdaEnabled() {
@@ -591,8 +614,7 @@
     }
 
     private void notifyCapabilityChanged() {
-        mNotifier.notifyPhoneCapabilityChanged(maybeOverrideMaxActiveVoiceSubscriptions(
-                mStaticCapability));
+        mNotifier.notifyPhoneCapabilityChanged(getStaticPhoneCapability());
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index 803fb19..d9c5c9c 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -187,7 +187,7 @@
                     Rlog.i(LOG_TAG, "Network Mode set to " + Integer.toString(networkModes[i]));
                     sCommandsInterfaces[i] = new RIL(context,
                             RadioAccessFamily.getRafFromNetworkType(networkModes[i]),
-                            cdmaSubscription, i);
+                            cdmaSubscription, i, featureFlags);
                 }
 
                 if (numPhones > 0) {
@@ -312,7 +312,7 @@
             for (int i = prevActiveModemCount; i < activeModemCount; i++) {
                 sCommandsInterfaces[i] = new RIL(context, RadioAccessFamily.getRafFromNetworkType(
                         RILConstants.PREFERRED_NETWORK_MODE),
-                        cdmaSubscription, i);
+                        cdmaSubscription, i, sFeatureFlags);
                 sPhones[i] = createPhone(context, i);
                 if (context.getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_TELEPHONY_IMS)) {
diff --git a/src/java/com/android/internal/telephony/PhoneNotifier.java b/src/java/com/android/internal/telephony/PhoneNotifier.java
index cb6b199..9f459f5 100644
--- a/src/java/com/android/internal/telephony/PhoneNotifier.java
+++ b/src/java/com/android/internal/telephony/PhoneNotifier.java
@@ -153,4 +153,7 @@
 
     /** Notify that simultaneous cellular calling subscriptions have changed */
     void notifySimultaneousCellularCallingSubscriptionsChanged(Set<Integer> subIds);
+
+    /** Notify carrier roaming non-terrestrial network mode changed. **/
+    void notifyCarrierRoamingNtnModeChanged(Phone sender, boolean active);
 }
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index 8b3be1e..8abebe2 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -88,6 +88,7 @@
 import com.android.internal.telephony.cdma.CdmaInformationRecords;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.emergency.EmergencyConstants;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.imsphone.ImsCallInfo;
 import com.android.internal.telephony.metrics.ModemRestartStats;
@@ -151,6 +152,15 @@
     public static final HalVersion RADIO_HAL_VERSION_UNKNOWN = HalVersion.UNKNOWN;
 
     /** @hide */
+    public static final HalVersion RADIO_HAL_VERSION_1_1 = new HalVersion(1, 1);
+
+    /** @hide */
+    public static final HalVersion RADIO_HAL_VERSION_1_2 = new HalVersion(1, 2);
+
+    /** @hide */
+    public static final HalVersion RADIO_HAL_VERSION_1_3 = new HalVersion(1, 3);
+
+    /** @hide */
     public static final HalVersion RADIO_HAL_VERSION_1_4 = new HalVersion(1, 4);
 
     /** @hide */
@@ -197,6 +207,8 @@
 
     boolean mIsRadioProxyInitialized = false;
 
+    Boolean mIsRadioVersion20Cached = null;
+
     // When we are testing emergency calls using ril.test.emergencynumber, this will trigger test
     // ECbM when the call is ended.
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -210,6 +222,8 @@
 
     public static final int MAX_SERVICE_IDX = HAL_SERVICE_IMS;
 
+    @NonNull private final FeatureFlags mFeatureFlags;
+
     /**
      * An array of sets that records if services are disabled in the HAL for a specific phone ID
      * slot to avoid further getService requests for that service. See XXX_SERVICE for the indices.
@@ -373,12 +387,27 @@
                 case EVENT_AIDL_PROXY_DEAD:
                     int aidlService = msg.arg1;
                     long msgCookie = (long) msg.obj;
-                    riljLog("handleMessage: EVENT_AIDL_PROXY_DEAD cookie = " + msgCookie
-                            + ", service = " + serviceToString(aidlService) + ", cookie = "
-                            + mServiceCookies.get(aidlService));
-                    if (msgCookie == mServiceCookies.get(aidlService).get()) {
-                        mIsRadioProxyInitialized = false;
-                        resetProxyAndRequestList(aidlService);
+                    if (mFeatureFlags.combineRilDeathHandle()) {
+                        if (msgCookie == mServiceCookies.get(aidlService).get()) {
+                            riljLog("handleMessage: EVENT_AIDL_PROXY_DEAD cookie = " + msgCookie
+                                    + ", service = " + serviceToString(aidlService) + ", cookie = "
+                                    + mServiceCookies.get(aidlService));
+                            mIsRadioProxyInitialized = false;
+                            resetProxyAndRequestList(aidlService);
+                            // Remove duplicate death message to avoid duplicate reset.
+                            mRilHandler.removeMessages(EVENT_AIDL_PROXY_DEAD);
+                        } else {
+                            riljLog("Ignore stale EVENT_AIDL_PROXY_DEAD for service "
+                                    + serviceToString(aidlService));
+                        }
+                    } else {
+                        riljLog("handleMessage: EVENT_AIDL_PROXY_DEAD cookie = " + msgCookie
+                                + ", service = " + serviceToString(aidlService) + ", cookie = "
+                                + mServiceCookies.get(aidlService));
+                        if (msgCookie == mServiceCookies.get(aidlService).get()) {
+                            mIsRadioProxyInitialized = false;
+                            resetProxyAndRequestList(aidlService);
+                        }
                     }
                     break;
             }
@@ -423,24 +452,33 @@
         public void serviceDied(long cookie) {
             // Deal with service going away
             riljLog("serviceDied");
-            mRilHandler.sendMessage(mRilHandler.obtainMessage(EVENT_RADIO_PROXY_DEAD,
-                    HAL_SERVICE_RADIO, 0 /* ignored arg2 */, cookie));
+            if (mFeatureFlags.combineRilDeathHandle()) {
+                mRilHandler.sendMessageAtFrontOfQueue(mRilHandler.obtainMessage(
+                        EVENT_RADIO_PROXY_DEAD,
+                        HAL_SERVICE_RADIO, 0 /* ignored arg2 */, cookie));
+            } else {
+                mRilHandler.sendMessage(mRilHandler.obtainMessage(EVENT_RADIO_PROXY_DEAD,
+                        HAL_SERVICE_RADIO, 0 /* ignored arg2 */, cookie));
+            }
         }
     }
 
     private final class BinderServiceDeathRecipient implements IBinder.DeathRecipient {
         private IBinder mBinder;
         private final int mService;
+        private long mLinkedFlags;
 
         BinderServiceDeathRecipient(int service) {
             mService = service;
+            mLinkedFlags = 0;
         }
 
         public void linkToDeath(IBinder service) throws RemoteException {
             if (service != null) {
                 riljLog("Linked to death for service " + serviceToString(mService));
                 mBinder = service;
-                mBinder.linkToDeath(this, (int) mServiceCookies.get(mService).incrementAndGet());
+                mLinkedFlags = mServiceCookies.get(mService).incrementAndGet();
+                mBinder.linkToDeath(this, (int) mLinkedFlags);
             } else {
                 riljLoge("Unable to link to death for service " + serviceToString(mService));
             }
@@ -448,32 +486,58 @@
 
         public synchronized void unlinkToDeath() {
             if (mBinder != null) {
-                mBinder.unlinkToDeath(this, 0);
+                mBinder.unlinkToDeath(this, (int) mLinkedFlags);
                 mBinder = null;
+                mLinkedFlags = 0;
             }
         }
 
         @Override
         public void binderDied() {
             riljLog("Service " + serviceToString(mService) + " has died.");
-            mRilHandler.sendMessage(mRilHandler.obtainMessage(EVENT_AIDL_PROXY_DEAD, mService,
-                    0 /* ignored arg2 */, mServiceCookies.get(mService).get()));
+            if (mFeatureFlags.combineRilDeathHandle()) {
+                mRilHandler.sendMessageAtFrontOfQueue(mRilHandler.obtainMessage(
+                        EVENT_AIDL_PROXY_DEAD, mService, 0 /* ignored arg2 */,
+                        mLinkedFlags));
+            } else {
+                mRilHandler.sendMessage(mRilHandler.obtainMessage(EVENT_AIDL_PROXY_DEAD, mService,
+                        0 /* ignored arg2 */, mLinkedFlags));
+            }
             unlinkToDeath();
         }
     }
 
-    private synchronized void resetProxyAndRequestList(int service) {
+    /**
+     * Reset services. If one of the AIDL service is reset, all the other AIDL services will be
+     * reset as well.
+     * @param service The service to reset.
+     */
+    private synchronized void resetProxyAndRequestList(@HalService int service) {
         if (service == HAL_SERVICE_RADIO) {
             mRadioProxy = null;
+            // Increment the cookie so that death notification can be ignored
+            mServiceCookies.get(service).incrementAndGet();
         } else {
-            mServiceProxies.get(service).clear();
+            if (mFeatureFlags.combineRilDeathHandle()) {
+                // Reset all aidl services.
+                for (int i = MIN_SERVICE_IDX; i <= MAX_SERVICE_IDX; i++) {
+                    if (i == HAL_SERVICE_RADIO) continue;
+                    if (mServiceProxies.get(i) == null) {
+                        // This should only happen in tests
+                        riljLoge("Null service proxy for service " + serviceToString(i));
+                        continue;
+                    }
+                    mServiceProxies.get(i).clear();
+                    // Increment the cookie so that death notification can be ignored
+                    mServiceCookies.get(i).incrementAndGet();
+                }
+            } else {
+                mServiceProxies.get(service).clear();
+                // Increment the cookie so that death notification can be ignored
+                mServiceCookies.get(service).incrementAndGet();
+            }
         }
 
-        // Increment the cookie so that death notification can be ignored
-        mServiceCookies.get(service).incrementAndGet();
-
-        // TODO: If a service doesn't exist or is unimplemented, it shouldn't cause the radio to
-        //  become unavailable for all other services
         setRadioState(TelephonyManager.RADIO_POWER_UNAVAILABLE, true /* forceNotifyRegistrants */);
 
         RILRequest.resetSerial();
@@ -483,7 +547,20 @@
         if (service == HAL_SERVICE_RADIO) {
             getRadioProxy();
         } else {
-            getRadioServiceProxy(service);
+            if (mFeatureFlags.combineRilDeathHandle()) {
+                // Reset all aidl services.
+                for (int i = MIN_SERVICE_IDX; i <= MAX_SERVICE_IDX; i++) {
+                    if (i == HAL_SERVICE_RADIO) continue;
+                    if (mServiceProxies.get(i) == null) {
+                        // This should only happen in tests
+                        riljLoge("Null service proxy for service " + serviceToString(i));
+                        continue;
+                    }
+                    getRadioServiceProxy(i);
+                }
+            } else {
+                getRadioServiceProxy(service);
+            }
         }
     }
 
@@ -501,10 +578,6 @@
             mMockModem = null;
 
             mMockModem = new MockModem(mContext, serviceName, mPhoneId);
-            if (mMockModem == null) {
-                riljLoge("MockModem create fail.");
-                return false;
-            }
 
             // Disable HIDL service
             if (mRadioProxy != null) {
@@ -541,8 +614,14 @@
 
             if (serviceBound) {
                 mIsRadioProxyInitialized = false;
-                for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) {
-                    resetProxyAndRequestList(service);
+                if (mFeatureFlags.combineRilDeathHandle()) {
+                    // Reset both hidl and aidl proxies.
+                    resetProxyAndRequestList(HAL_SERVICE_RADIO);
+                    resetProxyAndRequestList(HAL_SERVICE_DATA);
+                } else {
+                    for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) {
+                        resetProxyAndRequestList(service);
+                    }
                 }
             }
         }
@@ -570,7 +649,15 @@
                             mHalVersion.put(service, RADIO_HAL_VERSION_UNSUPPORTED);
                         }
                     }
-                    resetProxyAndRequestList(service);
+                    if (!mFeatureFlags.combineRilDeathHandle()) {
+                        resetProxyAndRequestList(service);
+                    }
+                }
+                if (mFeatureFlags.combineRilDeathHandle()) {
+                    // Reset both hidl and aidl proxies. Must be after cleaning mocked halVersion,
+                    // otherwise an aidl service will be incorrectly considered as disabled.
+                    resetProxyAndRequestList(HAL_SERVICE_RADIO);
+                    resetProxyAndRequestList(HAL_SERVICE_DATA);
                 }
             }
         }
@@ -728,9 +815,12 @@
     public synchronized RadioServiceProxy getRadioServiceProxy(int service) {
         if (!SubscriptionManager.isValidPhoneId(mPhoneId)) return mServiceProxies.get(service);
         if ((service >= HAL_SERVICE_IMS) && !isRadioServiceSupported(service)) {
-            riljLogw("getRadioServiceProxy: " + serviceToString(service) + " for "
-                    + HIDL_SERVICE_NAME[mPhoneId] + " is not supported\n"
-                    + android.util.Log.getStackTraceString(new RuntimeException()));
+            // Suppress the excessive logging for HAL_SERVICE_IMS when not supported.
+            if (service != HAL_SERVICE_IMS) {
+                riljLogw("getRadioServiceProxy: " + serviceToString(service) + " for "
+                        + HIDL_SERVICE_NAME[mPhoneId] + " is not supported\n"
+                        + android.util.Log.getStackTraceString(new RuntimeException()));
+            }
             return mServiceProxies.get(service);
         }
         if (!mIsCellularSupported) {
@@ -979,16 +1069,33 @@
     @Override
     public synchronized void onSlotActiveStatusChange(boolean active) {
         mIsRadioProxyInitialized = false;
-        for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) {
+        if (mFeatureFlags.combineRilDeathHandle()) {
             if (active) {
-                // Try to connect to RIL services and set response functions.
-                if (service == HAL_SERVICE_RADIO) {
-                    getRadioProxy();
-                } else {
-                    getRadioServiceProxy(service);
+                for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) {
+                    // Try to connect to RIL services and set response functions.
+                    if (service == HAL_SERVICE_RADIO) {
+                        getRadioProxy();
+                    } else {
+                        getRadioServiceProxy(service);
+                    }
                 }
             } else {
-                resetProxyAndRequestList(service);
+                // Reset both hidl and aidl proxies
+                resetProxyAndRequestList(HAL_SERVICE_RADIO);
+                resetProxyAndRequestList(HAL_SERVICE_DATA);
+            }
+        } else {
+            for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) {
+                if (active) {
+                    // Try to connect to RIL services and set response functions.
+                    if (service == HAL_SERVICE_RADIO) {
+                        getRadioProxy();
+                    } else {
+                        getRadioServiceProxy(service);
+                    }
+                } else {
+                    resetProxyAndRequestList(service);
+                }
             }
         }
     }
@@ -996,19 +1103,16 @@
     //***** Constructors
 
     @UnsupportedAppUsage
-    public RIL(Context context, int allowedNetworkTypes, int cdmaSubscription) {
-        this(context, allowedNetworkTypes, cdmaSubscription, null);
-    }
-
-    @UnsupportedAppUsage
-    public RIL(Context context, int allowedNetworkTypes, int cdmaSubscription, Integer instanceId) {
-        this(context, allowedNetworkTypes, cdmaSubscription, instanceId, null);
+    public RIL(Context context, int allowedNetworkTypes, int cdmaSubscription, Integer instanceId,
+            @NonNull FeatureFlags flags) {
+        this(context, allowedNetworkTypes, cdmaSubscription, instanceId, null, flags);
     }
 
     @VisibleForTesting
     public RIL(Context context, int allowedNetworkTypes, int cdmaSubscription, Integer instanceId,
-            SparseArray<RadioServiceProxy> proxies) {
+            SparseArray<RadioServiceProxy> proxies, @NonNull FeatureFlags flags) {
         super(context);
+        mFeatureFlags = flags;
         if (RILJ_LOGD) {
             riljLog("RIL: init allowedNetworkTypes=" + allowedNetworkTypes
                     + " cdmaSubscription=" + cdmaSubscription + ")");
@@ -1115,7 +1219,7 @@
         // Set radio callback; needed to set RadioIndication callback (should be done after
         // wakelock stuff is initialized above as callbacks are received on separate binder threads)
         for (int service = MIN_SERVICE_IDX; service <= MAX_SERVICE_IDX; service++) {
-            if (!isRadioServiceSupported(service)) {
+            if (isRadioVersion2_0() && !isRadioServiceSupported(service)) {
                 riljLog("Not initializing " + serviceToString(service) + " (not supported)");
                 continue;
             }
@@ -1137,12 +1241,13 @@
     }
 
     private boolean isRadioVersion2_0() {
+        if (mIsRadioVersion20Cached != null) return mIsRadioVersion20Cached;
         for (int service = HAL_SERVICE_DATA; service <= MAX_SERVICE_IDX; service++) {
             if (isRadioServiceSupported(service)) {
-                return true;
+                return mIsRadioVersion20Cached = true;
             }
         }
-        return false;
+        return mIsRadioVersion20Cached = false;
     }
 
     private boolean isRadioServiceSupported(int service) {
@@ -1283,17 +1388,6 @@
         } else if (proxy instanceof RadioImsProxy) {
             service = HAL_SERVICE_IMS;
         }
-
-        if (mHalVersion.get(service).less(version)) {
-            riljLoge(String.format("%s not supported on service %s < %s.",
-                    request, serviceToString(service), version));
-            if (result != null) {
-                AsyncResult.forMessage(result, null,
-                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
-                result.sendToTarget();
-            }
-            return false;
-        }
         if (proxy == null || proxy.isEmpty()) {
             riljLoge(String.format("Unable to complete %s because service %s is not available.",
                     request, serviceToString(service)));
@@ -1304,6 +1398,16 @@
             }
             return false;
         }
+        if (mHalVersion.get(service).less(version)) {
+            riljLoge(String.format("%s not supported on service %s < %s.",
+                    request, serviceToString(service), version));
+            if (result != null) {
+                AsyncResult.forMessage(result, null,
+                        CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
+                result.sendToTarget();
+            }
+            return false;
+        }
         return true;
     }
 
diff --git a/src/java/com/android/internal/telephony/RadioConfig.java b/src/java/com/android/internal/telephony/RadioConfig.java
index 13f6502..da20639 100644
--- a/src/java/com/android/internal/telephony/RadioConfig.java
+++ b/src/java/com/android/internal/telephony/RadioConfig.java
@@ -61,10 +61,6 @@
 
     static final int EVENT_HIDL_SERVICE_DEAD = 1;
     static final int EVENT_AIDL_SERVICE_DEAD = 2;
-    static final HalVersion RADIO_CONFIG_HAL_VERSION_UNKNOWN = new HalVersion(-1, -1);
-    static final HalVersion RADIO_CONFIG_HAL_VERSION_1_1 = new HalVersion(1, 1);
-    static final HalVersion RADIO_CONFIG_HAL_VERSION_1_3 = new HalVersion(1, 3);
-    static final HalVersion RADIO_CONFIG_HAL_VERSION_2_0 = new HalVersion(2, 0);
 
     private final boolean mIsMobileNetworkSupported;
     private final SparseArray<RILRequest> mRequestList = new SparseArray<>();
@@ -294,13 +290,12 @@
 
         if (service != null) {
             mRadioConfigProxy.setAidl(
-                    RADIO_CONFIG_HAL_VERSION_2_0,
                     android.hardware.radio.config.IRadioConfig.Stub.asInterface(service));
         }
 
         if (mRadioConfigProxy.isEmpty()) {
             try {
-                mRadioConfigProxy.setHidl(RADIO_CONFIG_HAL_VERSION_1_3,
+                mRadioConfigProxy.setHidl(RIL.RADIO_HAL_VERSION_1_3,
                         android.hardware.radio.config.V1_3.IRadioConfig.getService(true));
             } catch (RemoteException | NoSuchElementException e) {
                 mRadioConfigProxy.clear();
@@ -310,7 +305,7 @@
 
         if (mRadioConfigProxy.isEmpty()) {
             try {
-                mRadioConfigProxy.setHidl(RADIO_CONFIG_HAL_VERSION_1_1,
+                mRadioConfigProxy.setHidl(RIL.RADIO_HAL_VERSION_1_1,
                         android.hardware.radio.config.V1_1.IRadioConfig.getService(true));
             } catch (RemoteException | NoSuchElementException e) {
                 mRadioConfigProxy.clear();
@@ -515,7 +510,7 @@
         RadioConfigProxy proxy = getRadioConfigProxy(null);
         if (proxy.isEmpty()) return;
 
-        if (proxy.getVersion().less(RADIO_CONFIG_HAL_VERSION_1_1)) {
+        if (proxy.getVersion().less(RIL.RADIO_HAL_VERSION_1_1)) {
             if (result != null) {
                 AsyncResult.forMessage(result, null,
                         CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
@@ -543,7 +538,7 @@
      */
     public boolean isSetPreferredDataCommandSupported() {
         RadioConfigProxy proxy = getRadioConfigProxy(null);
-        return !proxy.isEmpty() && proxy.getVersion().greaterOrEqual(RADIO_CONFIG_HAL_VERSION_1_1);
+        return !proxy.isEmpty() && proxy.getVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_1);
     }
 
     /**
@@ -574,7 +569,7 @@
         RadioConfigProxy proxy = getRadioConfigProxy(result);
         if (proxy.isEmpty()) return;
 
-        if (proxy.getVersion().less(RADIO_CONFIG_HAL_VERSION_1_1)) {
+        if (proxy.getVersion().less(RIL.RADIO_HAL_VERSION_1_1)) {
             if (result != null) {
                 AsyncResult.forMessage(
                         result, null, CommandException.fromRilErrno(REQUEST_NOT_SUPPORTED));
@@ -628,7 +623,7 @@
         RadioConfigProxy proxy = getRadioConfigProxy(Message.obtain(result));
         if (proxy.isEmpty()) return;
 
-        if (proxy.getVersion().less(RADIO_CONFIG_HAL_VERSION_1_3)) {
+        if (proxy.getVersion().less(RIL.RADIO_HAL_VERSION_1_3)) {
             if (result != null) {
                 if (DBG) {
                     logd("RIL_REQUEST_GET_HAL_DEVICE_CAPABILITIES > REQUEST_NOT_SUPPORTED");
diff --git a/src/java/com/android/internal/telephony/RadioConfigIndicationAidl.java b/src/java/com/android/internal/telephony/RadioConfigIndicationAidl.java
index 9aa1aaa..127631d 100644
--- a/src/java/com/android/internal/telephony/RadioConfigIndicationAidl.java
+++ b/src/java/com/android/internal/telephony/RadioConfigIndicationAidl.java
@@ -17,14 +17,14 @@
 package com.android.internal.telephony;
 
 import android.os.AsyncResult;
-import android.os.RemoteException;
 import android.os.Trace;
 
 import com.android.internal.telephony.uicc.IccSlotStatus;
 import com.android.telephony.Rlog;
 
 import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * This class is the AIDL implementation of IRadioConfigIndication interface.
@@ -58,9 +58,11 @@
      */
     @Override
     public void onSimultaneousCallingSupportChanged(int[] enabledLogicalSlots) {
-        ArrayList<Integer> ret = RILUtils.primitiveArrayToArrayList(enabledLogicalSlots);
+        List<Integer> ret = (enabledLogicalSlots == null) ? Collections.emptyList() :
+                RILUtils.primitiveArrayToArrayList(enabledLogicalSlots);
         logd("onSimultaneousCallingSupportChanged: enabledLogicalSlots = " + ret);
         if (mRadioConfig.mSimultaneousCallingSupportStatusRegistrant != null) {
+            logd("onSimultaneousCallingSupportChanged: notifying registrant");
             mRadioConfig.mSimultaneousCallingSupportStatusRegistrant.notifyRegistrant(
                     new AsyncResult(null, ret, null));
         }
diff --git a/src/java/com/android/internal/telephony/RadioConfigProxy.java b/src/java/com/android/internal/telephony/RadioConfigProxy.java
index b6c6d68..9f34e29 100644
--- a/src/java/com/android/internal/telephony/RadioConfigProxy.java
+++ b/src/java/com/android/internal/telephony/RadioConfigProxy.java
@@ -31,14 +31,15 @@
  * downstream users.
  */
 public class RadioConfigProxy {
-    private final HalVersion mRadioHalVersion;
+    private static final String TAG = "RadioConfigProxy";
+    private HalVersion mRadioHalVersion;
     private final RadioConfigHidlServiceDeathRecipient mRadioConfigHidlServiceDeathRecipient;
     private final RadioConfigAidlServiceDeathRecipient mRadioConfigAidlServiceDeathRecipient;
 
     private volatile android.hardware.radio.config.V1_1.IRadioConfig mHidlRadioConfigProxy = null;
     private volatile android.hardware.radio.config.IRadioConfig mAidlRadioConfigProxy = null;
 
-    private HalVersion mRadioConfigHalVersion = RadioConfig.RADIO_CONFIG_HAL_VERSION_UNKNOWN;
+    private HalVersion mRadioConfigHalVersion = RIL.RADIO_HAL_VERSION_UNKNOWN;
     private boolean mIsAidl;
 
     public RadioConfigProxy(RadioConfig radioConfig, HalVersion radioHalVersion) {
@@ -83,13 +84,15 @@
     /**
      * Set IRadioConfig as the AIDL implementation for RadioConfigProxy
      *
-     * @param radioConfigHalVersion RadioConfig HAL version
      * @param radioConfig IRadioConfig implementation
      */
-    public void setAidl(
-            HalVersion radioConfigHalVersion,
-            android.hardware.radio.config.IRadioConfig radioConfig) {
-        mRadioConfigHalVersion = radioConfigHalVersion;
+    public void setAidl(android.hardware.radio.config.IRadioConfig radioConfig) {
+        try {
+            mRadioConfigHalVersion = RIL.getServiceHalVersion(radioConfig.getInterfaceVersion());
+            Rlog.d(TAG, "setAidl: setting HAL version to version = " + mRadioConfigHalVersion);
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "setAidl: " + e);
+        }
         mAidlRadioConfigProxy = radioConfig;
         mIsAidl = true;
         mRadioConfigAidlServiceDeathRecipient.setService(radioConfig.asBinder());
@@ -106,7 +109,7 @@
 
     /** Reset RadioConfigProxy */
     public void clear() {
-        mRadioConfigHalVersion = RadioConfig.RADIO_CONFIG_HAL_VERSION_UNKNOWN;
+        mRadioConfigHalVersion = RIL.RADIO_HAL_VERSION_UNKNOWN;
         mHidlRadioConfigProxy = null;
         mAidlRadioConfigProxy = null;
         mRadioConfigHidlServiceDeathRecipient.clear();
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 498535b..8764e02 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -1054,7 +1054,8 @@
             tracker.onSent(mContext);
             mPhone.notifySmsSent(tracker.mDestAddress);
             mSmsDispatchersController.notifySmsSentToEmergencyStateTracker(
-                    tracker.mDestAddress, tracker.mMessageId, false);
+                    tracker.mDestAddress, tracker.mMessageId, false,
+                    tracker.isSinglePartOrLastPart());
 
             mPhone.getSmsStats().onOutgoingSms(
                     tracker.mImsRetry > 0 /* isOverIms */,
@@ -1063,7 +1064,8 @@
                     SmsManager.RESULT_ERROR_NONE,
                     tracker.mMessageId,
                     tracker.isFromDefaultSmsApplication(mContext),
-                    tracker.getInterval());
+                    tracker.getInterval(),
+                    mTelephonyManager.isEmergencyNumber(tracker.mDestAddress));
             if (mPhone != null) {
                 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                 if (telephonyAnalytics != null) {
@@ -1113,7 +1115,8 @@
                         getNotInServiceError(ss),
                         tracker.mMessageId,
                         tracker.isFromDefaultSmsApplication(mContext),
-                        tracker.getInterval());
+                        tracker.getInterval(),
+                        mTelephonyManager.isEmergencyNumber(tracker.mDestAddress));
                 if (mPhone != null) {
                     TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                     if (telephonyAnalytics != null) {
@@ -1149,7 +1152,8 @@
                         errorCode,
                         tracker.mMessageId,
                         tracker.isFromDefaultSmsApplication(mContext),
-                        tracker.getInterval());
+                        tracker.getInterval(),
+                        mTelephonyManager.isEmergencyNumber(tracker.mDestAddress));
                 if (mPhone != null) {
                     TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                     if (telephonyAnalytics != null) {
@@ -1175,7 +1179,8 @@
                         errorCode,
                         tracker.mMessageId,
                         tracker.isFromDefaultSmsApplication(mContext),
-                        tracker.getInterval());
+                        tracker.getInterval(),
+                        mTelephonyManager.isEmergencyNumber(tracker.mDestAddress));
                 if (mPhone != null) {
                     TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                     if (telephonyAnalytics != null) {
@@ -2403,7 +2408,8 @@
                     error,
                     trackers[0].mMessageId,
                     trackers[0].isFromDefaultSmsApplication(mContext),
-                    trackers[0].getInterval());
+                    trackers[0].getInterval(),
+                    mTelephonyManager.isEmergencyNumber(trackers[0].mDestAddress));
             if (mPhone != null) {
                 TelephonyAnalytics telephonyAnalytics = mPhone.getTelephonyAnalytics();
                 if (telephonyAnalytics != null) {
@@ -2608,6 +2614,14 @@
         }
 
         /**
+         * Returns the flag specifying whether this {@link SmsTracker} is a single part or
+         * the last part of multipart message.
+         */
+        protected boolean isSinglePartOrLastPart() {
+            return mUnsentPartCount != null ? (mUnsentPartCount.get() == 0) : true;
+        }
+
+        /**
          * Persist a sent SMS if required:
          * 1. It is a text message
          * 2. SmsApplication tells us to persist: sent from apps that are not default-SMS app or
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index ba6479e..2a9bf7f 100644
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -227,7 +227,6 @@
     private final RegistrantList mAreaCodeChangedRegistrants = new RegistrantList();
 
     /* Radio power off pending flag */
-    // @GuardedBy("this")
     private volatile boolean mPendingRadioPowerOffAfterDataOff = false;
 
     /** Waiting period before recheck gprs and voice registration. */
@@ -3728,10 +3727,18 @@
             // because operatorNumeric would be SIM's mcc/mnc when device is on IWLAN), but if the
             // device has camped on a cell either to attempt registration or for emergency services,
             // then for purposes of setting the locale, we don't care if registration fails or is
-            // incomplete.
+            // incomplete. Additionally, if there is no cellular service and ims is registered over
+            // the IWLAN, the locale will not be updated.
             // CellIdentity can return a null MCC and MNC in CDMA
             String localeOperator = operatorNumeric;
-            if (mSS.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN) {
+            int dataNetworkType = mSS.getDataNetworkType();
+            if (dataNetworkType == TelephonyManager.NETWORK_TYPE_IWLAN
+                    || (dataNetworkType == TelephonyManager.NETWORK_TYPE_UNKNOWN
+                            && getImsRegistrationTech()
+                                    == ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN)) {
+                // TODO(b/333346537#comment10): Complete solution would be ignore mcc/mnc reported
+                //  by the unsolicited indication OPERATOR from RIL, but only relies on MCC/MNC from
+                //  data registration or voice registration.
                 localeOperator = null;
             }
             if (isInvalidOperatorNumeric(localeOperator)) {
@@ -5008,20 +5015,10 @@
     }
 
     /**
-     * process the pending request to turn radio off after data is disconnected
-     *
-     * return true if there is pending request to process; false otherwise.
+     * return true if there is pending disconnect data request to process; false otherwise.
      */
-    public boolean processPendingRadioPowerOffAfterDataOff() {
-        synchronized(this) {
-            if (mPendingRadioPowerOffAfterDataOff) {
-                if (DBG) log("Process pending request to turn radio off.");
-                hangupAndPowerOff();
-                mPendingRadioPowerOffAfterDataOff = false;
-                return true;
-            }
-            return false;
-        }
+    public boolean isPendingRadioPowerOffAfterDataOff() {
+        return mPendingRadioPowerOffAfterDataOff;
     }
 
     private void onCarrierConfigurationChanged(int slotIndex) {
@@ -5911,4 +5908,17 @@
     public @Nullable CellIdentity getLastKnownCellIdentity() {
         return mLastKnownCellIdentity;
     }
+
+    /**
+     * Get the tech where ims is currently registered.
+     * @return Returns the tech of ims registered. if not registered or no phome for ims, returns
+     *   {@link ImsRegistrationImplBase#REGISTRATION_TECH_NONE}.
+     */
+    private @ImsRegistrationImplBase.ImsRegistrationTech int getImsRegistrationTech() {
+        ImsPhone imsPhone = (ImsPhone) mPhone.getImsPhone();
+        if (imsPhone != null) {
+            return imsPhone.getImsRegistrationTech();
+        }
+        return ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/SignalStrengthController.java b/src/java/com/android/internal/telephony/SignalStrengthController.java
index b11d7e5..98f84b2 100644
--- a/src/java/com/android/internal/telephony/SignalStrengthController.java
+++ b/src/java/com/android/internal/telephony/SignalStrengthController.java
@@ -50,6 +50,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.internal.telephony.util.ArrayUtils;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.telephony.Rlog;
 
@@ -60,6 +61,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.UUID;
@@ -101,7 +103,7 @@
     private static final int EVENT_GET_SIGNAL_STRENGTH                      = 6;
     private static final int EVENT_POLL_SIGNAL_STRENGTH                     = 7;
     private static final int EVENT_SIGNAL_STRENGTH_UPDATE                   = 8;
-    private static final int EVENT_POLL_SIGNAL_STRENGTH_DONE                = 9;
+    public static final int EVENT_POLL_SIGNAL_STRENGTH_DONE                = 9;
     private static final int EVENT_SERVICE_STATE_CHANGED                    = 10;
 
     @NonNull
@@ -330,7 +332,7 @@
      * @param signalStrength The new SignalStrength used for updating {@code mSignalStrength}.
      */
     private void updateSignalStrength(@NonNull SignalStrength signalStrength) {
-        mSignalStrength = signalStrength;
+        mSignalStrength = maybeOverrideSignalStrengthForTest(signalStrength);
         ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker();
         if (serviceStateTracker != null) {
             mSignalStrength.updateLevel(mCarrierConfig, serviceStateTracker.mSS);
@@ -342,6 +344,18 @@
     }
 
     /**
+     * For debug test build, override signal strength for testing.
+     * @param original The real signal strength to use if not in testing mode.
+     * @return The signal strength to broadcast to the external.
+     */
+    @NonNull private SignalStrength maybeOverrideSignalStrengthForTest(
+            @NonNull SignalStrength original) {
+        return TelephonyUtils.IS_DEBUGGABLE && mPhone.getTelephonyTester() != null
+                ? Objects.requireNonNullElse(mPhone.getTelephonyTester()
+                .getOverriddenSignalStrength(), original) : original;
+    }
+
+    /**
      * @return signal strength
      */
     @NonNull
@@ -750,7 +764,7 @@
     }
 
     void setSignalStrengthDefaultValues() {
-        mSignalStrength = new SignalStrength();
+        mSignalStrength = maybeOverrideSignalStrengthForTest(new SignalStrength());
         mSignalStrengthUpdatedTime = System.currentTimeMillis();
     }
 
diff --git a/src/java/com/android/internal/telephony/SimultaneousCallingTracker.java b/src/java/com/android/internal/telephony/SimultaneousCallingTracker.java
index 0a14ccd..0b427f8 100644
--- a/src/java/com/android/internal/telephony/SimultaneousCallingTracker.java
+++ b/src/java/com/android/internal/telephony/SimultaneousCallingTracker.java
@@ -494,7 +494,7 @@
                 l.onSimultaneousCallingSupportChanged(simultaneousCallSubscriptionIdMap);
             }
         } catch (Exception e) {
-            Log.w(LOG_TAG, "handleVideoCapabilitiesChanged: Exception = " + e);
+            Log.w(LOG_TAG, "handleSimultaneousCallingSupportChanged: Exception = " + e);
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
index 7fc499e..077ee0b 100644
--- a/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
+++ b/src/java/com/android/internal/telephony/SmsBroadcastUndelivered.java
@@ -30,6 +30,7 @@
 import android.os.UserManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.analytics.TelephonyAnalytics;
 import com.android.internal.telephony.analytics.TelephonyAnalytics.SmsMmsAnalytics;
@@ -243,7 +244,8 @@
                             message.mMessageCount);
                     if (phone != null) {
                         phone.getSmsStats().onDroppedIncomingMultipartSms(message.mIs3gpp2, rows,
-                                message.mMessageCount);
+                                message.mMessageCount, TelephonyManager.from(context)
+                                        .isEmergencyNumber(message.mAddress));
                         TelephonyAnalytics telephonyAnalytics = phone.getTelephonyAnalytics();
                         if (telephonyAnalytics != null) {
                             SmsMmsAnalytics smsMmsAnalytics =
diff --git a/src/java/com/android/internal/telephony/SmsController.java b/src/java/com/android/internal/telephony/SmsController.java
index 32c7429..59184d8 100644
--- a/src/java/com/android/internal/telephony/SmsController.java
+++ b/src/java/com/android/internal/telephony/SmsController.java
@@ -302,16 +302,17 @@
         SubscriptionInfo info;
         try {
             info = getSubscriptionInfo(subId);
+
+            if (isBluetoothSubscription(info)) {
+                sendBluetoothText(info, destAddr, text, sentIntent, deliveryIntent);
+            } else {
+                sendIccText(subId, callingPackage, destAddr, scAddr, text, sentIntent,
+                        deliveryIntent, persistMessageForNonDefaultSmsApp, messageId,
+                        skipShortCodeCheck);
+            }
         } finally {
             Binder.restoreCallingIdentity(token);
         }
-
-        if (isBluetoothSubscription(info)) {
-            sendBluetoothText(info, destAddr, text, sentIntent, deliveryIntent);
-        } else {
-            sendIccText(subId, callingPackage, destAddr, scAddr, text, sentIntent, deliveryIntent,
-                    persistMessageForNonDefaultSmsApp, messageId, skipShortCodeCheck);
-        }
     }
 
     private boolean isBluetoothSubscription(SubscriptionInfo info) {
diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java
index cc287f8..bc1e1a8 100644
--- a/src/java/com/android/internal/telephony/SmsDispatchersController.java
+++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java
@@ -112,6 +112,9 @@
     /** Called when MT SMS is received via IMS. */
     private static final int EVENT_SMS_RECEIVED_VIA_IMS = 21;
 
+    /** Called when the domain selection should be performed. */
+    private static final int EVENT_REQUEST_DOMAIN_SELECTION = 22;
+
     /** 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 */
@@ -491,9 +494,10 @@
                 Long messageId = (Long) args.arg2;
                 Boolean success = (Boolean) args.arg3;
                 Boolean isOverIms = (Boolean) args.arg4;
+                Boolean isLastSmsPart = (Boolean) args.arg5;
                 try {
                     handleSmsSentCompletedUsingDomainSelection(
-                            destAddr, messageId, success, isOverIms);
+                            destAddr, messageId, success, isOverIms, isLastSmsPart);
                 } finally {
                     args.recycle();
                 }
@@ -508,6 +512,19 @@
                 handleSmsReceivedViaIms((String) msg.obj);
                 break;
             }
+            case EVENT_REQUEST_DOMAIN_SELECTION: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                DomainSelectionConnectionHolder holder =
+                        (DomainSelectionConnectionHolder) args.arg1;
+                PendingRequest request = (PendingRequest) args.arg2;
+                String logTag = (String) args.arg3;
+                try {
+                    requestDomainSelection(holder, request, logTag);
+                } finally {
+                    args.recycle();
+                }
+                break;
+            }
             default:
                 if (isCdmaMo()) {
                     mCdmaDispatcher.handleMessage(msg);
@@ -762,18 +779,18 @@
 
         if (!tracker.mUsesImsServiceForIms) {
             if (isSmsDomainSelectionEnabled()) {
-                DomainSelectionConnectionHolder holder = getDomainSelectionConnection(false);
-
-                // If the DomainSelectionConnection is not available,
-                // fallback to the legacy implementation.
-                if (holder != null && holder.getConnection() != null) {
-                    sendSmsUsingDomainSelection(holder,
-                            new PendingRequest(PendingRequest.TYPE_RETRY_SMS, tracker,
-                                    null, null, null, null, null, false, null, 0, null, null, false,
-                                    0, false, 0, 0L, false),
-                            "sendRetrySms");
-                    return;
-                }
+                TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+                boolean isEmergency = tm.isEmergencyNumber(tracker.mDestAddress);
+                // This may be invoked by another thread, so this operation is posted and
+                // handled through the execution flow of SmsDispatchersController.
+                SomeArgs args = SomeArgs.obtain();
+                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);
+                args.arg3 = "sendRetrySms";
+                sendMessage(obtainMessage(EVENT_REQUEST_DOMAIN_SELECTION, args));
+                return;
             }
 
             if (mImsSmsDispatcher.isAvailable()) {
@@ -984,52 +1001,23 @@
      * Returns a {@link DomainSelectionConnectionHolder} according to the flag specified.
      *
      * @param emergency The flag to indicate that the domain selection is for an emergency SMS.
-     * @return A {@link DomainSelectionConnectionHolder} instance or null.
+     * @return A {@link DomainSelectionConnectionHolder} instance.
      */
     @VisibleForTesting
     @Nullable
     protected DomainSelectionConnectionHolder getDomainSelectionConnectionHolder(
             boolean emergency) {
-        return emergency ? mEmergencyDscHolder : mDscHolder;
-    }
-
-    /**
-     * Returns a {@link DomainSelectionConnectionHolder} if the domain selection supports,
-     * return null otherwise.
-     *
-     * @param emergency The flag to indicate that the domain selection is for an emergency SMS.
-     * @return A {@link DomainSelectionConnectionHolder} that grabs the
-     *         {@link DomainSelectionConnection} and its related information to use the domain
-     *         selection architecture.
-     */
-    private DomainSelectionConnectionHolder getDomainSelectionConnection(boolean emergency) {
-        DomainSelectionConnectionHolder holder = getDomainSelectionConnectionHolder(emergency);
-        DomainSelectionConnection connection = (holder != null) ? holder.getConnection() : null;
-
-        if (connection == null) {
-            connection = mDomainSelectionResolverProxy.getDomainSelectionConnection(
-                    mPhone, DomainSelectionService.SELECTOR_TYPE_SMS, emergency);
-
-            if (connection == null) {
-                // Domain selection architecture is not supported.
-                // Use the legacy architecture.
-                return null;
+        if (emergency) {
+            if (mEmergencyDscHolder == null) {
+                mEmergencyDscHolder = new DomainSelectionConnectionHolder(emergency);
             }
-        }
-
-        if (holder == null) {
-            holder = new DomainSelectionConnectionHolder(emergency);
-
-            if (emergency) {
-                mEmergencyDscHolder = holder;
-            } else {
-                mDscHolder = holder;
+            return mEmergencyDscHolder;
+        } else {
+            if (mDscHolder == null) {
+                mDscHolder = new DomainSelectionConnectionHolder(emergency);
             }
+            return mDscHolder;
         }
-
-        holder.setConnection(connection);
-
-        return holder;
     }
 
     /**
@@ -1079,6 +1067,8 @@
      *
      * @param holder The {@link DomainSelectionConnectionHolder} that contains the
      *               {@link DomainSelectionConnection} and its related information.
+     * @param request The {@link PendingRequest} that stores the SMS request
+     *                (data, text, multipart text) to be sent.
      * @param logTag The log string.
      */
     private void requestDomainSelection(@NonNull DomainSelectionConnectionHolder holder,
@@ -1088,6 +1078,21 @@
         // the domain selection by adding this request to the pending list.
         holder.addRequest(request);
 
+        if (holder.getConnection() == null) {
+            DomainSelectionConnection connection =
+                    mDomainSelectionResolverProxy.getDomainSelectionConnection(
+                            mPhone, DomainSelectionService.SELECTOR_TYPE_SMS, holder.isEmergency());
+            if (connection == null) {
+                logd("requestDomainSelection: fallback for " + logTag);
+                // If the domain selection connection is not available,
+                // fallback to the legacy implementation.
+                sendAllPendingRequests(holder, NetworkRegistrationInfo.DOMAIN_UNKNOWN);
+                return;
+            } else {
+                holder.setConnection(connection);
+            }
+        }
+
         if (!isDomainSelectionRequested) {
             if (VDBG) {
                 logd("requestDomainSelection: " + logTag);
@@ -1157,15 +1162,17 @@
      * @param messageId The message id for SMS.
      * @param success A flag specifying whether MO SMS is successfully sent or not.
      * @param isOverIms A flag specifying whether MO SMS is sent over IMS or not.
+     * @param isLastSmsPart A flag specifying whether this result is for the last SMS part or not.
      */
     private void handleSmsSentCompletedUsingDomainSelection(@NonNull String destAddr,
-            long messageId, boolean success, boolean isOverIms) {
+            long messageId, boolean success, boolean isOverIms, boolean isLastSmsPart) {
         if (mEmergencyStateTracker != null) {
             TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
             if (tm.isEmergencyNumber(destAddr)) {
                 mEmergencyStateTracker.endSms(String.valueOf(messageId), success,
                         isOverIms ? NetworkRegistrationInfo.DOMAIN_PS
-                                  : NetworkRegistrationInfo.DOMAIN_CS);
+                                  : NetworkRegistrationInfo.DOMAIN_CS,
+                        isLastSmsPart);
             }
         }
     }
@@ -1174,7 +1181,7 @@
      * Called when MO SMS is successfully sent.
      */
     protected void notifySmsSentToEmergencyStateTracker(@NonNull String destAddr, long messageId,
-            boolean isOverIms) {
+            boolean isOverIms, boolean isLastSmsPart) {
         if (isSmsDomainSelectionEnabled()) {
             // Run on main thread for interworking with EmergencyStateTracker.
             SomeArgs args = SomeArgs.obtain();
@@ -1182,6 +1189,7 @@
             args.arg2 = Long.valueOf(messageId);
             args.arg3 = Boolean.TRUE;
             args.arg4 = Boolean.valueOf(isOverIms);
+            args.arg5 = Boolean.valueOf(isLastSmsPart);
             sendMessage(obtainMessage(EVENT_SMS_SENT_COMPLETED_USING_DOMAIN_SELECTION, args));
         }
     }
@@ -1198,6 +1206,7 @@
             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));
         }
     }
@@ -1544,19 +1553,13 @@
         }
 
         if (isSmsDomainSelectionEnabled()) {
-            DomainSelectionConnectionHolder holder = getDomainSelectionConnection(false);
-
-            // If the DomainSelectionConnection is not available,
-            // fallback to the legacy implementation.
-            if (holder != null && holder.getConnection() != null) {
-                sendSmsUsingDomainSelection(holder,
-                        new PendingRequest(PendingRequest.TYPE_DATA, null, callingPackage,
-                                destAddr, scAddr, asArrayList(sentIntent),
-                                asArrayList(deliveryIntent), isForVvm, data, destPort, null, null,
-                                false, 0, false, 0, 0L, false),
-                        "sendData");
-                return;
-            }
+            sendSmsUsingDomainSelection(getDomainSelectionConnectionHolder(false),
+                    new PendingRequest(PendingRequest.TYPE_DATA, null, callingPackage,
+                            destAddr, scAddr, asArrayList(sentIntent),
+                            asArrayList(deliveryIntent), isForVvm, data, destPort, null, null,
+                            false, 0, false, 0, 0L, false),
+                    "sendData");
+            return;
         }
 
         if (mImsSmsDispatcher.isAvailable()) {
@@ -1785,20 +1788,14 @@
         if (isSmsDomainSelectionEnabled()) {
             TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
             boolean isEmergency = tm.isEmergencyNumber(destAddr);
-            DomainSelectionConnectionHolder holder = getDomainSelectionConnection(isEmergency);
-
-            // If the DomainSelectionConnection is not available,
-            // fallback to the legacy implementation.
-            if (holder != null && holder.getConnection() != null) {
-                sendSmsUsingDomainSelection(holder,
-                        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");
-                return;
-            }
+            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");
+            return;
         }
 
         if (mImsSmsDispatcher.isAvailable() || mImsSmsDispatcher.isEmergencySmsSupport(destAddr)) {
@@ -1932,19 +1929,15 @@
         }
 
         if (isSmsDomainSelectionEnabled()) {
-            DomainSelectionConnectionHolder holder = getDomainSelectionConnection(false);
-
-            // If the DomainSelectionConnection is not available,
-            // fallback to the legacy implementation.
-            if (holder != null && holder.getConnection() != null) {
-                sendSmsUsingDomainSelection(holder,
-                        new PendingRequest(PendingRequest.TYPE_MULTIPART_TEXT, null,
-                                callingPkg, destAddr, scAddr, sentIntents, deliveryIntents, false,
-                                null, 0, parts, messageUri, persistMessage, priority, expectMore,
-                                validityPeriod, messageId, false),
-                        "sendMultipartText");
-                return;
-            }
+            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+            boolean isEmergency = tm.isEmergencyNumber(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");
+            return;
         }
 
         if (mImsSmsDispatcher.isAvailable()) {
diff --git a/src/java/com/android/internal/telephony/TelephonyCapabilities.java b/src/java/com/android/internal/telephony/TelephonyCapabilities.java
index 1b4a3a9..71d3b14 100644
--- a/src/java/com/android/internal/telephony/TelephonyCapabilities.java
+++ b/src/java/com/android/internal/telephony/TelephonyCapabilities.java
@@ -16,9 +16,12 @@
 
 package com.android.internal.telephony;
 
+import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
+import android.os.SystemProperties;
 
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.telephony.Rlog;
 
 /**
@@ -194,4 +197,16 @@
     public static boolean canDistinguishDialingAndConnected(int phoneType) {
         return phoneType == PhoneConstants.PHONE_TYPE_GSM;
     }
+
+    /**
+     * Returns true if Calling/Data/Messaging features should be checked on this device.
+     */
+    public static boolean minimalTelephonyCdmCheck(@NonNull FeatureFlags featureFlags) {
+        // Check SDK version of the vendor partition.
+        final int vendorApiLevel = SystemProperties.getInt(
+                "ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
+        if (vendorApiLevel < Build.VERSION_CODES.VANILLA_ICE_CREAM) return false;
+
+        return featureFlags.minimalTelephonyCdmCheck();
+    }
 }
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index 5da4b12..0b0f9d3 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -523,8 +523,8 @@
     /**
      * Create a new LinkBandwidthEstimator.
      */
-    public LinkBandwidthEstimator makeLinkBandwidthEstimator(Phone phone) {
-        return new LinkBandwidthEstimator(phone, mTelephonyFacade);
+    public LinkBandwidthEstimator makeLinkBandwidthEstimator(Phone phone, Looper looper) {
+        return new LinkBandwidthEstimator(phone, looper, mTelephonyFacade);
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/TelephonyTester.java b/src/java/com/android/internal/telephony/TelephonyTester.java
index b9e04c8..7d3d75d 100644
--- a/src/java/com/android/internal/telephony/TelephonyTester.java
+++ b/src/java/com/android/internal/telephony/TelephonyTester.java
@@ -17,16 +17,21 @@
 package com.android.internal.telephony;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.Uri;
+import android.os.AsyncResult;
 import android.os.BadParcelableException;
 import android.os.Bundle;
+import android.os.PersistableBundle;
 import android.telephony.AccessNetworkConstants;
+import android.telephony.CellSignalStrengthLte;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsConferenceState;
@@ -46,6 +51,7 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -163,6 +169,7 @@
     private static List<ImsExternalCallState> mImsExternalCallStates = null;
 
     private Intent mServiceStateTestIntent;
+    private SignalStrengthTestable mSignalStrengthTest;
 
     private Phone mPhone;
 
@@ -386,11 +393,68 @@
     }
 
     /**
+     * Testable signal strength that mocks its fields.
+     */
+    private class SignalStrengthTestable extends SignalStrength {
+        private SignalStrengthTestable() {
+            super();
+        }
+
+        public void mockLevel(int level) {
+            try {
+                Field lteField = SignalStrength.class.getDeclaredField("mLte");
+                lteField.setAccessible(true);
+                CellSignalStrengthLte lte = (CellSignalStrengthLte) lteField.get(this);
+
+                Field lvlField = CellSignalStrengthLte.class.getDeclaredField("mLevel");
+                lvlField.setAccessible(true);
+                lvlField.set(lte, level);
+            } catch (Exception e) {
+                log("SignalStrengthTestable: mockLevel " + e);
+            }
+        }
+
+        @Override
+        public void updateLevel(PersistableBundle cc, ServiceState ss) {
+            log("SignalStrengthTestable: updateLevel: do nothing ");
+        }
+
+        @Override
+        public String toString() {
+            return "SignalStrengthTestable-" + getLevel();
+        }
+    }
+
+    /** {@link android.telephony.SignalStrength} */
+    public void setSignalStrength(int level) {
+        if (level > -1) {
+            log("setSignalStrength: level " + level);
+            mSignalStrengthTest = new SignalStrengthTestable();
+            mSignalStrengthTest.mockLevel(level);
+            AsyncResult ar = new AsyncResult(null, mSignalStrengthTest, null);
+            mPhone.getSignalStrengthController().sendMessage(mPhone.getSignalStrengthController()
+                    .obtainMessage(SignalStrengthController.EVENT_POLL_SIGNAL_STRENGTH_DONE, ar));
+        } else {
+            log("setSignalStrength: clear mock");
+            mSignalStrengthTest = null;
+            mPhone.getSignalStrengthController().getSignalStrengthFromCi();
+        }
+    }
+
+    /** {@link android.telephony.SignalStrength} */
+    @Nullable
+    public SignalStrength getOverriddenSignalStrength() {
+        return mSignalStrengthTest;
+    }
+
+    /**
      * Set the service state test intent.
      *
      * @param intent The service state test intent.
      */
     public void setServiceStateTestIntent(@NonNull Intent intent) {
+        // Don't process if the intent is not prepared for this phone slot.
+        if (mPhone.getPhoneId() != intent.getIntExtra(EXTRA_PHONE_ID, mPhone.getPhoneId())) return;
         mServiceStateTestIntent = intent;
         // Trigger the service state update. The replacement will be done in
         // overrideServiceState().
@@ -400,10 +464,6 @@
 
     void overrideServiceState(ServiceState ss) {
         if (mServiceStateTestIntent == null || ss == null) return;
-        if (mPhone.getPhoneId() != mServiceStateTestIntent.getIntExtra(
-                EXTRA_PHONE_ID, mPhone.getPhoneId())) {
-            return;
-        }
         if (mServiceStateTestIntent.hasExtra(EXTRA_ACTION)
                 && ACTION_RESET.equals(mServiceStateTestIntent.getStringExtra(EXTRA_ACTION))) {
             log("Service state override reset");
diff --git a/src/java/com/android/internal/telephony/cat/CatService.java b/src/java/com/android/internal/telephony/cat/CatService.java
index cadb02e..4da1622 100644
--- a/src/java/com/android/internal/telephony/cat/CatService.java
+++ b/src/java/com/android/internal/telephony/cat/CatService.java
@@ -284,6 +284,14 @@
             CatLog.d(this, "Disposing CatService object");
             mIccRecords.unregisterForRecordsLoaded(this);
 
+            if (sFlags.unregisterSmsBroadcastReceiverFromCatService()) {
+                try {
+                    mContext.unregisterReceiver(mSmsBroadcastReceiver);
+                } catch (IllegalArgumentException e) {
+                    CatLog.e(this, "mSmsBroadcastReceiver: was not registered" + e);
+                }
+            }
+
             // Clean up stk icon if dispose is called
             broadcastCardStateAndIccRefreshResp(CardState.CARDSTATE_ABSENT, null);
 
diff --git a/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java b/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
index 4b10cae..f9261ff 100644
--- a/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
+++ b/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
@@ -24,6 +24,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.telephony.uicc.IccFileHandler;
 import com.android.internal.telephony.uicc.IccUtils;
 import com.android.internal.util.State;
@@ -40,11 +41,14 @@
     private static final int CMD_START = 1;
     private static final int CMD_PARAMS_READY = 2;
 
+    private final Object mLock = new Object();
     // members
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @GuardedBy("mLock")
     private CommandParamsFactory mCmdParamsFactory = null;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private RilMessage mCurrentRilMessage = null;
+    @GuardedBy("mLock")
     private Handler mCaller = null;
     private static int mSimCount = 0;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -113,9 +117,13 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void sendCmdForExecution(RilMessage rilMsg) {
-        Message msg = mCaller.obtainMessage(CatService.MSG_ID_RIL_MSG_DECODED,
-                new RilMessage(rilMsg));
-        msg.sendToTarget();
+        synchronized (mLock) {
+            if (mCaller != null) {
+                Message msg = mCaller.obtainMessage(CatService.MSG_ID_RIL_MSG_DECODED,
+                        new RilMessage(rilMsg));
+                msg.sendToTarget();
+            }
+        }
     }
 
     private RilMessageDecoder(Handler caller, IccFileHandler fh, Context context) {
@@ -125,8 +133,10 @@
         addState(mStateCmdParamsReady);
         setInitialState(mStateStart);
 
-        mCaller = caller;
-        mCmdParamsFactory = CommandParamsFactory.getInstance(this, fh, context);
+        synchronized (mLock) {
+            mCaller = caller;
+            mCmdParamsFactory = CommandParamsFactory.getInstance(this, fh, context);
+        }
     }
 
     private RilMessageDecoder() {
@@ -166,7 +176,7 @@
     }
 
     private boolean decodeMessageParams(RilMessage rilMsg) {
-        boolean decodingStarted;
+        boolean decodingStarted = false;
 
         mCurrentRilMessage = rilMsg;
         switch(rilMsg.mId) {
@@ -188,16 +198,21 @@
                 decodingStarted = false;
                 break;
             }
-            try {
-                // Start asynch parsing of the command parameters.
-                mCmdParamsFactory.make(BerTlv.decode(rawData));
-                decodingStarted = true;
-            } catch (ResultException e) {
-                // send to Service for proper RIL communication.
-                CatLog.d(this, "decodeMessageParams: caught ResultException e=" + e);
-                mCurrentRilMessage.mResCode = e.result();
-                sendCmdForExecution(mCurrentRilMessage);
-                decodingStarted = false;
+
+            synchronized (mLock) {
+                if (mCmdParamsFactory != null) {
+                    try {
+                        // Start asynch parsing of the command parameters.
+                        mCmdParamsFactory.make(BerTlv.decode(rawData));
+                        decodingStarted = true;
+                    } catch (ResultException e) {
+                        // send to Service for proper RIL communication.
+                        CatLog.d(this, "decodeMessageParams: caught ResultException e=" + e);
+                        mCurrentRilMessage.mResCode = e.result();
+                        sendCmdForExecution(mCurrentRilMessage);
+                        decodingStarted = false;
+                    }
+                }
             }
             break;
         default:
@@ -211,10 +226,16 @@
         quitNow();
         mStateStart = null;
         mStateCmdParamsReady = null;
-        mCmdParamsFactory.dispose();
-        mCmdParamsFactory = null;
+
+        synchronized (mLock) {
+            if (mCmdParamsFactory != null) {
+                mCmdParamsFactory.dispose();
+                mCmdParamsFactory = null;
+            }
+            mCaller = null;
+        }
+
         mCurrentRilMessage = null;
-        mCaller = null;
         mInstance = null;
     }
 }
diff --git a/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java b/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java
index 0db7844..85413f5 100644
--- a/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java
+++ b/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java
@@ -20,12 +20,16 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
+import android.os.FileUtils;
 import android.util.Log;
-import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.satellite.SatelliteConfig;
 import com.android.internal.telephony.satellite.SatelliteConfigParser;
+import com.android.internal.telephony.satellite.SatelliteConstants;
+import com.android.internal.telephony.satellite.metrics.ConfigUpdaterMetricsStats;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.server.updates.ConfigUpdateInstallReceiver;
 
 import libcore.io.IoUtils;
@@ -33,6 +37,8 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 
@@ -43,7 +49,8 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected static final String UPDATE_DIR = "/data/misc/telephonyconfig";
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    protected static final String UPDATE_CONTENT_PATH = "telephony_config.pb";
+    protected static final String NEW_CONFIG_CONTENT_PATH = "new_telephony_config.pb";
+    protected static final String VALID_CONFIG_CONTENT_PATH = "valid_telephony_config.pb";
     protected static final String UPDATE_METADATA_PATH = "metadata/";
     public static final String VERSION = "version";
 
@@ -52,6 +59,7 @@
     private final Object mConfigParserLock = new Object();
     @GuardedBy("mConfigParserLock")
     private ConfigParser mConfigParser;
+    @NonNull private final ConfigUpdaterMetricsStats mConfigUpdaterMetricsStats;
 
 
     public static TelephonyConfigUpdateInstallReceiver sReceiverAdaptorInstance =
@@ -66,7 +74,8 @@
     }
 
     public TelephonyConfigUpdateInstallReceiver() {
-        super(UPDATE_DIR, UPDATE_CONTENT_PATH, UPDATE_METADATA_PATH, VERSION);
+        super(UPDATE_DIR, NEW_CONFIG_CONTENT_PATH, UPDATE_METADATA_PATH, VERSION);
+        mConfigUpdaterMetricsStats = ConfigUpdaterMetricsStats.getOrCreateInstance();
     }
 
     /**
@@ -74,57 +83,105 @@
      */
     @Nullable
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public byte[] getCurrentContent() {
+    public byte[] getContentFromContentPath(@NonNull File contentPath) {
         try {
-            return IoUtils.readFileAsByteArray(updateContent.getCanonicalPath());
+            return IoUtils.readFileAsByteArray(contentPath.getCanonicalPath());
         } catch (IOException e) {
-            Slog.i(TAG, "Failed to read current content, assuming first update!");
+            Log.e(TAG, "Failed to read current content : " + contentPath);
             return null;
         }
     }
 
+    /**
+     * @param parser target of validation.
+     * @return {@code true} if all the config data are valid {@code false} otherwise.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean isValidSatelliteCarrierConfigData(@NonNull ConfigParser parser) {
+        SatelliteConfig satelliteConfig = (SatelliteConfig) parser.getConfig();
+        if (satelliteConfig == null) {
+            Log.e(TAG, "satelliteConfig is null");
+            mConfigUpdaterMetricsStats.reportOemAndCarrierConfigError(
+                    SatelliteConstants.CONFIG_UPDATE_RESULT_NO_SATELLITE_DATA);
+            return false;
+        }
+
+        // If no carrier config exist then it is considered as a valid config
+        Set<Integer> carrierIds = satelliteConfig.getAllSatelliteCarrierIds();
+        for (int carrierId : carrierIds) {
+            Map<String, Set<Integer>> plmnsServices =
+                    satelliteConfig.getSupportedSatelliteServices(carrierId);
+            Set<String> plmns = plmnsServices.keySet();
+            for (String plmn : plmns) {
+                if (!TelephonyUtils.isValidPlmn(plmn)) {
+                    Log.e(TAG, "found invalid plmn : " + plmn);
+                    mConfigUpdaterMetricsStats.reportCarrierConfigError(
+                            SatelliteConstants.CONFIG_UPDATE_RESULT_CARRIER_DATA_INVALID_PLMN);
+                    return false;
+                }
+                Set<Integer> serviceSet = plmnsServices.get(plmn);
+                for (int service : serviceSet) {
+                    if (!TelephonyUtils.isValidService(service)) {
+                        Log.e(TAG, "found invalid service : " + service);
+                        mConfigUpdaterMetricsStats.reportCarrierConfigError(SatelliteConstants
+                                .CONFIG_UPDATE_RESULT_CARRIER_DATA_INVALID_SUPPORTED_SERVICES);
+                        return false;
+                    }
+                }
+            }
+        }
+        Log.d(TAG, "the config data is valid");
+        return true;
+    }
+
+
     @Override
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
     public void postInstall(Context context, Intent intent) {
         Log.d(TAG, "Telephony config is updated in file partition");
-        ConfigParser updatedConfigParser = getNewConfigParser(DOMAIN_SATELLITE,
-                getCurrentContent());
 
-        if (updatedConfigParser == null) {
-            Log.d(TAG, "updatedConfigParser is null");
+        ConfigParser newConfigParser = getNewConfigParser(DOMAIN_SATELLITE,
+                getContentFromContentPath(updateContent));
+
+        if (newConfigParser == null) {
+            Log.e(TAG, "newConfigParser is null");
             return;
         }
 
-        boolean isParserChanged = false;
+        if (!isValidSatelliteCarrierConfigData(newConfigParser)) {
+            Log.e(TAG, "received config data has invalid satellite carrier config data");
+            return;
+        }
 
         synchronized (getInstance().mConfigParserLock) {
-            if (getInstance().mConfigParser == null) {
-                getInstance().mConfigParser = updatedConfigParser;
-                isParserChanged = true;
-            } else {
-                int updatedVersion = updatedConfigParser.mVersion;
+            if (getInstance().mConfigParser != null) {
+                int updatedVersion = newConfigParser.mVersion;
                 int previousVersion = getInstance().mConfigParser.mVersion;
                 Log.d(TAG, "previous version is " + previousVersion + " | updated version is "
                         + updatedVersion);
-                if (updatedVersion > previousVersion) {
-                    getInstance().mConfigParser = updatedConfigParser;
-                    isParserChanged = true;
+                mConfigUpdaterMetricsStats.setConfigVersion(updatedVersion);
+                if (updatedVersion <= previousVersion) {
+                    Log.e(TAG, "updatedVersion is smaller than previousVersion");
+                    mConfigUpdaterMetricsStats.reportOemAndCarrierConfigError(
+                            SatelliteConstants.CONFIG_UPDATE_RESULT_INVALID_VERSION);
+                    return;
                 }
             }
+            getInstance().mConfigParser = newConfigParser;
+        }
+
+        if (!getInstance().mCallbackHashMap.keySet().isEmpty()) {
+            Iterator<Executor> iterator = getInstance().mCallbackHashMap.keySet().iterator();
+            while (iterator.hasNext()) {
+                Executor executor = iterator.next();
+                getInstance().mCallbackHashMap.get(executor).onChanged(newConfigParser);
+            }
         }
 
-        if (isParserChanged) {
-            if (getInstance().mCallbackHashMap.keySet().isEmpty()) {
-                Log.d(TAG, "mCallbackHashMap.keySet().isEmpty");
-                return;
-            }
-            Iterator<Executor> iterator =
-                    getInstance().mCallbackHashMap.keySet().iterator();
-            while (iterator.hasNext()) {
-                Executor executor = iterator.next();
-                getInstance().mCallbackHashMap.get(executor).onChanged(
-                        updatedConfigParser);
-            }
+        if (!copySourceFileToTargetFile(NEW_CONFIG_CONTENT_PATH, VALID_CONFIG_CONTENT_PATH)) {
+            Log.e(TAG, "fail to copy to the valid satellite carrier config data");
+            mConfigUpdaterMetricsStats.reportOemAndCarrierConfigError(
+                    SatelliteConstants.CONFIG_UPDATE_RESULT_IO_ERROR);
         }
     }
 
@@ -135,7 +192,9 @@
         synchronized (getInstance().mConfigParserLock) {
             if (getInstance().mConfigParser == null) {
                 Log.d(TAG, "CreateNewConfigParser with domain " + domain);
-                getInstance().mConfigParser = getNewConfigParser(domain, getCurrentContent());
+                getInstance().mConfigParser = getNewConfigParser(
+                        domain, getContentFromContentPath(new File(updateDir,
+                                VALID_CONFIG_CONTENT_PATH)));
             }
             return getInstance().mConfigParser;
         }
@@ -187,6 +246,8 @@
     public ConfigParser getNewConfigParser(String domain, @Nullable byte[] data) {
         if (data == null) {
             Log.d(TAG, "content data is null");
+            mConfigUpdaterMetricsStats.reportOemAndCarrierConfigError(
+                    SatelliteConstants.CONFIG_UPDATE_RESULT_NO_DATA);
             return null;
         }
         switch (domain) {
@@ -194,7 +255,40 @@
                 return new SatelliteConfigParser(data);
             default:
                 Log.e(TAG, "DOMAIN should be specified");
+                mConfigUpdaterMetricsStats.reportOemAndCarrierConfigError(
+                        SatelliteConstants.CONFIG_UPDATE_RESULT_INVALID_DOMAIN);
                 return null;
         }
     }
+
+    /**
+     * @param sourceFileName source file name
+     * @param targetFileName target file name
+     * @return {@code true} if successful, {@code false} otherwise
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public boolean copySourceFileToTargetFile(
+            @NonNull String sourceFileName, @NonNull String targetFileName) {
+        try {
+            File sourceFile = new File(UPDATE_DIR, sourceFileName);
+            File targetFile = new File(UPDATE_DIR, targetFileName);
+            Log.d(TAG, "copy " + sourceFile.getName() + " >> " + targetFile.getName());
+
+            if (sourceFile.exists()) {
+                if (targetFile.exists()) {
+                    targetFile.delete();
+                }
+                FileUtils.copy(sourceFile, targetFile);
+                FileUtils.copyPermissions(sourceFile, targetFile);
+                Log.d(TAG, "success to copy the file " + sourceFile.getName() + " to "
+                        + targetFile.getName());
+                return true;
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "copy error : " + e);
+            return false;
+        }
+        Log.d(TAG, "source file is not exist, no file to copy");
+        return false;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
index 3d3fbe9..bc684af 100644
--- a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
+++ b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
@@ -69,7 +69,6 @@
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
-import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 /**
@@ -86,7 +85,8 @@
     /**
      * The counters to detect frequent QNS attempt to change preferred network transport by ApnType.
      */
-    private final @NonNull SparseArray<SlidingWindowEventCounter> mApnTypeToQnsChangeNetworkCounter;
+    @NonNull
+    private final SparseArray<SlidingWindowEventCounter> mApnTypeToQnsChangeNetworkCounter;
 
     private final String mLogTag;
     private final LocalLog mLocalLog = new LocalLog(64);
@@ -109,12 +109,11 @@
 
     private final CarrierConfigManager mCarrierConfigManager;
 
-    private @Nullable DataConfigManager mDataConfigManager;
+    @Nullable
+    private DataConfigManager mDataConfigManager;
 
     private IQualifiedNetworksService mIQualifiedNetworksService;
 
-    private AccessNetworksManagerDeathRecipient mDeathRecipient;
-
     private String mTargetBindingPackageName;
 
     private QualifiedNetworksServiceConnection mServiceConnection;
@@ -122,7 +121,8 @@
     // Available networks. Key is the APN type.
     private final SparseArray<int[]> mAvailableNetworks = new SparseArray<>();
 
-    private final @TransportType int[] mAvailableTransports;
+    @TransportType
+    private final int[] mAvailableTransports;
 
     private final RegistrantList mQualifiedNetworksChangedRegistrants = new RegistrantList();
 
@@ -135,7 +135,8 @@
     /**
      * Callbacks for passing information to interested clients.
      */
-    private final @NonNull Set<AccessNetworksManagerCallback> mAccessNetworksManagerCallbacks =
+    @NonNull
+    private final Set<AccessNetworksManagerCallback> mAccessNetworksManagerCallbacks =
             new ArraySet<>();
 
     private final FeatureFlags mFeatureFlags;
@@ -144,9 +145,12 @@
      * Represents qualified network types list on a specific APN type.
      */
     public static class QualifiedNetworks {
-        public final @ApnType int apnType;
+        @ApnType
+        public final int apnType;
         // The qualified networks in preferred order. Each network is a AccessNetworkType.
-        public final @NonNull @RadioAccessNetworkType int[] qualifiedNetworks;
+        @NonNull
+        @RadioAccessNetworkType
+        public final int[] qualifiedNetworks;
         public QualifiedNetworks(@ApnType int apnType, @NonNull int[] qualifiedNetworks) {
             this.apnType = apnType;
             this.qualifiedNetworks = Arrays.stream(qualifiedNetworks)
@@ -198,11 +202,12 @@
         public void onServiceConnected(ComponentName name, IBinder service) {
             if (DBG) log("onServiceConnected " + name);
             mIQualifiedNetworksService = IQualifiedNetworksService.Stub.asInterface(service);
-            mDeathRecipient = new AccessNetworksManagerDeathRecipient();
+            AccessNetworksManagerDeathRecipient deathRecipient =
+                    new AccessNetworksManagerDeathRecipient();
             mLastBoundPackageName = getQualifiedNetworksServicePackageName();
 
             try {
-                service.linkToDeath(mDeathRecipient, 0 /* flags */);
+                service.linkToDeath(deathRecipient, 0 /* flags */);
                 mIQualifiedNetworksService.createNetworkAvailabilityProvider(mPhone.getPhoneId(),
                         new QualifiedNetworksServiceCallback());
             } catch (RemoteException e) {
@@ -310,7 +315,7 @@
 
         /**
          * Called when QualifiedNetworksService requests network validation.
-         *
+         * <p>
          * Since the data network in the connected state corresponding to the given network
          * capability must be validated, a request is tossed to the data network controller.
          * @param networkCapability network capability
@@ -328,27 +333,22 @@
             log("onNetworkValidationRequested: networkCapability = ["
                     + DataUtils.networkCapabilityToString(networkCapability) + "]");
 
-            dnc.requestNetworkValidation(networkCapability, new Consumer<Integer>() {
-                @Override
-                public void accept(Integer result) {
-                    post(() -> {
-                        try {
-                            log("onNetworkValidationRequestDone:"
-                                    + DataServiceCallback.resultCodeToString(result));
-                            resultCodeCallback.accept(result.intValue());
-                        } catch (RemoteException e) {
-                            // Ignore if the remote process is no longer available to call back.
-                            loge("onNetworkValidationRequestDone RemoteException" + e);
-                        }
-                    });
+            dnc.requestNetworkValidation(networkCapability, result -> post(() -> {
+                try {
+                    log("onNetworkValidationRequestDone:"
+                            + DataServiceCallback.resultCodeToString(result));
+                    resultCodeCallback.accept(result);
+                } catch (RemoteException e) {
+                    // Ignore if the remote process is no longer available to call back.
+                    loge("onNetworkValidationRequestDone RemoteException" + e);
                 }
-            });
+            }));
         }
 
         @Override
-        public void onReconnectQualifedNetworkType(int apnTypes, int qualifiedNetworkType) {
+        public void onReconnectQualifiedNetworkType(int apnTypes, int qualifiedNetworkType) {
             if (mFeatureFlags.reconnectQualifiedNetwork()) {
-                log("onReconnectQualifedNetworkType: apnTypes = ["
+                log("onReconnectQualifiedNetworkType: apnTypes = ["
                         + ApnSetting.getApnTypesStringFromBitmask(apnTypes)
                         + "], networks = [" + AccessNetworkType.toString(qualifiedNetworkType)
                         + "]");
@@ -431,7 +431,8 @@
             mPhone.getDataNetworkController().getDataRetryManager().registerCallback(
                     new DataRetryManager.DataRetryManagerCallback(this::post) {
                         @Override
-                        public void onThrottleStatusChanged(List<ThrottleStatus> throttleStatuses) {
+                        public void onThrottleStatusChanged(
+                                @NonNull List<ThrottleStatus> throttleStatuses) {
                             try {
                                 logl("onThrottleStatusChanged: " + throttleStatuses);
                                 if (mIQualifiedNetworksService != null) {
@@ -473,7 +474,7 @@
      */
     private void bindQualifiedNetworksService() {
         post(() -> {
-            Intent intent = null;
+            Intent intent;
             String packageName = getQualifiedNetworksServicePackageName();
             String className = getQualifiedNetworksServiceClassName();
 
@@ -540,7 +541,7 @@
             b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId(),
                     CarrierConfigManager
                             .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING);
-            if (b != null && !b.isEmpty()) {
+            if (!b.isEmpty()) {
                 // If carrier config overrides it, use the one from carrier config
                 String carrierConfigPackageName = b.getString(CarrierConfigManager
                         .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_PACKAGE_OVERRIDE_STRING);
@@ -571,7 +572,7 @@
             b = mCarrierConfigManager.getConfigForSubId(mPhone.getSubId(),
                     CarrierConfigManager
                             .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_CLASS_OVERRIDE_STRING);
-            if (b != null && !b.isEmpty()) {
+            if (!b.isEmpty()) {
                 // If carrier config overrides it, use the one from carrier config
                 String carrierConfigClassName = b.getString(CarrierConfigManager
                         .KEY_CARRIER_QUALIFIED_NETWORKS_SERVICE_CLASS_OVERRIDE_STRING);
@@ -587,7 +588,8 @@
         return className;
     }
 
-    private @NonNull List<QualifiedNetworks> getQualifiedNetworksList() {
+    @NonNull
+    private List<QualifiedNetworks> getQualifiedNetworksList() {
         List<QualifiedNetworks> qualifiedNetworksList = new ArrayList<>();
         for (int i = 0; i < mAvailableNetworks.size(); i++) {
             qualifiedNetworksList.add(new QualifiedNetworks(mAvailableNetworks.keyAt(i),
@@ -619,11 +621,13 @@
     /**
      * @return The available transports.
      */
-    public @NonNull int[] getAvailableTransports() {
+    @NonNull
+    public int[] getAvailableTransports() {
         return mAvailableTransports;
     }
 
-    private static @TransportType int getTransportFromAccessNetwork(int accessNetwork) {
+    @TransportType
+    private static int getTransportFromAccessNetwork(int accessNetwork) {
         return accessNetwork == AccessNetworkType.IWLAN
                 ? AccessNetworkConstants.TRANSPORT_TYPE_WLAN
                 : AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
@@ -656,7 +660,8 @@
      * @param apnType APN type
      * @return The preferred transport.
      */
-    public @TransportType int getPreferredTransport(@ApnType int apnType) {
+    @TransportType
+    public int getPreferredTransport(@ApnType int apnType) {
         return mPreferredTransports.get(apnType) == null
                 ? AccessNetworkConstants.TRANSPORT_TYPE_WWAN : mPreferredTransports.get(apnType);
     }
@@ -668,8 +673,8 @@
      * supported.)
      * @return The preferred transport.
      */
-    public @TransportType int getPreferredTransportByNetworkCapability(
-            @NetCapability int networkCapability) {
+    @TransportType
+    public int getPreferredTransportByNetworkCapability(@NetCapability int networkCapability) {
         int apnType = DataUtils.networkCapabilityToApnType(networkCapability);
         // For non-APN type capabilities, always route to WWAN.
         if (apnType == ApnSetting.TYPE_NONE) {
diff --git a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
index 343bb0b..7486b61 100644
--- a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
+++ b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
@@ -137,17 +137,26 @@
     private static final long RETRY_LONG_DELAY_TIMER_THRESHOLD_MILLIS = TimeUnit
             .MINUTES.toMillis(1);
 
-    private final @NonNull LocalLog mLocalLog = new LocalLog(128);
-    private final @NonNull Context mContext;
-    private static @NonNull FeatureFlags sFeatureFlags = new FeatureFlagsImpl();
-    private final @NonNull SubscriptionManagerService mSubscriptionManagerService;
-    private final @NonNull PhoneSwitcher mPhoneSwitcher;
-    private final @NonNull AutoDataSwitchControllerCallback mPhoneSwitcherCallback;
-    private final @NonNull AlarmManager mAlarmManager;
+    @NonNull
+    private final LocalLog mLocalLog = new LocalLog(128);
+    @NonNull
+    private final Context mContext;
+    @NonNull
+    private static FeatureFlags sFeatureFlags = new FeatureFlagsImpl();
+    @NonNull
+    private final SubscriptionManagerService mSubscriptionManagerService;
+    @NonNull
+    private final PhoneSwitcher mPhoneSwitcher;
+    @NonNull
+    private final AutoDataSwitchControllerCallback mPhoneSwitcherCallback;
+    @NonNull
+    private final AlarmManager mAlarmManager;
     /** A map of a scheduled event to its associated extra for action when the event fires off. */
-    private final @NonNull Map<Integer, Object> mScheduledEventsToExtras;
+    @NonNull
+    private final Map<Integer, Object> mScheduledEventsToExtras;
     /** A map of an event to its associated alarm listener callback for when the event fires off. */
-    private final @NonNull Map<Integer, AlarmManager.OnAlarmListener> mEventsToAlarmListener;
+    @NonNull
+    private final Map<Integer, AlarmManager.OnAlarmListener> mEventsToAlarmListener;
     /**
      * Event extras for checking environment stability.
      * @param targetPhoneId The target phone Id to switch to when the stability check pass.
@@ -193,7 +202,7 @@
      * To indicate whether allow using roaming nDDS if user enabled its roaming when the DDS is not
      * usable(OOS or disabled roaming)
      */
-    private boolean mAllowNddsRoamning = true;
+    private boolean mAllowNddsRoaming = true;
     /** The count of consecutive auto switch validation failure **/
     private int mAutoSwitchValidationFailedCount = 0;
     /**
@@ -202,7 +211,8 @@
     private int mAutoDataSwitchValidationMaxRetry;
 
     /** The signal status of phones, where index corresponds to phone Id. */
-    private @NonNull PhoneSignalStatus[] mPhonesSignalStatus;
+    @NonNull
+    private PhoneSignalStatus[] mPhonesSignalStatus;
     /**
      * The phone Id of the pending switching phone. Used for pruning frequent switch evaluation.
      */
@@ -268,23 +278,24 @@
             boolean isUsingNonTerrestrialNetwork = sFeatureFlags.carrierEnabledSatelliteFlag()
                     && (serviceState != null) && serviceState.isUsingNonTerrestrialNetwork();
 
-            switch (mDataRegState) {
-                case NetworkRegistrationInfo.REGISTRATION_STATE_HOME:
+            return switch (mDataRegState) {
+                case NetworkRegistrationInfo.REGISTRATION_STATE_HOME -> {
                     if (isUsingNonTerrestrialNetwork) {
-                        return UsableState.NON_TERRESTRIAL;
+                        yield UsableState.NON_TERRESTRIAL;
                     }
-                    return UsableState.HOME;
-                case NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING:
+                    yield UsableState.HOME;
+                }
+                case NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING -> {
                     if (mPhone.getDataRoamingEnabled()) {
                         if (isUsingNonTerrestrialNetwork) {
-                            return UsableState.NON_TERRESTRIAL;
+                            yield UsableState.NON_TERRESTRIAL;
                         }
-                        return UsableState.ROAMING_ENABLED;
+                        yield UsableState.ROAMING_ENABLED;
                     }
-                    return UsableState.NOT_USABLE;
-                default:
-                    return UsableState.NOT_USABLE;
-            }
+                    yield UsableState.NOT_USABLE;
+                }
+                default -> UsableState.NOT_USABLE;
+            };
         }
 
         @Override
@@ -450,7 +461,7 @@
         DataConfigManager dataConfig = phone.getDataNetworkController().getDataConfigManager();
         mScoreTolerance =  dataConfig.getAutoDataSwitchScoreTolerance();
         mRequirePingTestBeforeSwitch = dataConfig.isPingTestBeforeAutoDataSwitchRequired();
-        mAllowNddsRoamning = dataConfig.doesAutoDataSwitchAllowRoaming();
+        mAllowNddsRoaming = dataConfig.doesAutoDataSwitchAllowRoaming();
         mAutoDataSwitchAvailabilityStabilityTimeThreshold =
                 dataConfig.getAutoDataSwitchAvailabilityStabilityTimeThreshold();
         mAutoDataSwitchPerformanceStabilityTimeThreshold =
@@ -462,6 +473,7 @@
     @Override
     public void handleMessage(@NonNull Message msg) {
         AsyncResult ar;
+        Object obj;
         int phoneId;
         switch (msg.what) {
             case EVENT_SERVICE_STATE_CHANGED:
@@ -480,33 +492,20 @@
                 onSignalStrengthChanged(phoneId);
                 break;
             case EVENT_EVALUATE_AUTO_SWITCH:
-                if (sFeatureFlags.autoDataSwitchRatSs()) {
-                    Object obj = mScheduledEventsToExtras.get(EVENT_EVALUATE_AUTO_SWITCH);
-                    if (obj instanceof EvaluateEventExtra extra) {
-                        mScheduledEventsToExtras.remove(EVENT_EVALUATE_AUTO_SWITCH);
-                        onEvaluateAutoDataSwitch(extra.evaluateReason);
-                    }
-                } else {
-                    int reason = (int) msg.obj;
-                    onEvaluateAutoDataSwitch(reason);
+                obj = mScheduledEventsToExtras.get(EVENT_EVALUATE_AUTO_SWITCH);
+                if (obj instanceof EvaluateEventExtra extra) {
+                    mScheduledEventsToExtras.remove(EVENT_EVALUATE_AUTO_SWITCH);
+                    onEvaluateAutoDataSwitch(extra.evaluateReason);
                 }
                 break;
             case EVENT_STABILITY_CHECK_PASSED:
-                if (sFeatureFlags.autoDataSwitchRatSs()) {
-                    Object obj = mScheduledEventsToExtras.get(EVENT_STABILITY_CHECK_PASSED);
-                    if (obj instanceof StabilityEventExtra extra) {
-                        int targetPhoneId = extra.targetPhoneId;
-                        boolean needValidation = extra.needValidation;
-                        log("require validation on phone " + targetPhoneId
-                                + (needValidation ? "" : " no") + " need to pass");
-                        mScheduledEventsToExtras.remove(EVENT_STABILITY_CHECK_PASSED);
-                        mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
-                    }
-                } else {
-                    int targetPhoneId = msg.arg1;
-                    boolean needValidation = msg.arg2 == 1;
+                obj = mScheduledEventsToExtras.get(EVENT_STABILITY_CHECK_PASSED);
+                if (obj instanceof StabilityEventExtra extra) {
+                    int targetPhoneId = extra.targetPhoneId;
+                    boolean needValidation = extra.needValidation;
                     log("require validation on phone " + targetPhoneId
                             + (needValidation ? "" : " no") + " need to pass");
+                    mScheduledEventsToExtras.remove(EVENT_STABILITY_CHECK_PASSED);
                     mPhoneSwitcherCallback.onRequireValidation(targetPhoneId, needValidation);
                 }
                 break;
@@ -632,15 +631,9 @@
                 ? mAutoDataSwitchAvailabilityStabilityTimeThreshold
                 << mAutoSwitchValidationFailedCount
                 : 0;
-        if (sFeatureFlags.autoDataSwitchRatSs()) {
-            if (!mScheduledEventsToExtras.containsKey(EVENT_EVALUATE_AUTO_SWITCH)) {
-                scheduleEventWithTimer(EVENT_EVALUATE_AUTO_SWITCH, new EvaluateEventExtra(reason),
-                        delayMs);
-            }
-        } else {
-            if (!hasMessages(EVENT_EVALUATE_AUTO_SWITCH)) {
-                sendMessageDelayed(obtainMessage(EVENT_EVALUATE_AUTO_SWITCH, reason), delayMs);
-            }
+        if (!mScheduledEventsToExtras.containsKey(EVENT_EVALUATE_AUTO_SWITCH)) {
+            scheduleEventWithTimer(EVENT_EVALUATE_AUTO_SWITCH, new EvaluateEventExtra(reason),
+                    delayMs);
         }
     }
 
@@ -689,12 +682,32 @@
                 return;
             }
 
-            if (!defaultDataPhone.isUserDataEnabled() || !backupDataPhone.isDataAllowed()) {
-                mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX,
-                        EVALUATION_REASON_DATA_SETTINGS_CHANGED);
-                log(debugMessage.append(", immediately back to default as user turns off settings")
-                        .toString());
-                return;
+            DataEvaluation internetEvaluation;
+            if (sFeatureFlags.autoDataSwitchUsesDataEnabled()) {
+                if (!defaultDataPhone.isUserDataEnabled()) {
+                    mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX,
+                            EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+                    log(debugMessage.append(
+                            ", immediately back to default as user turns off default").toString());
+                    return;
+                } else if (!(internetEvaluation = backupDataPhone.getDataNetworkController()
+                        .getInternetEvaluation(false/*ignoreExistingNetworks*/))
+                        .isSubsetOf(DataEvaluation.DataDisallowedReason.NOT_IN_SERVICE)) {
+                    mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(
+                            DEFAULT_PHONE_INDEX, EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+                    log(debugMessage.append(
+                                    ", immediately back to default because backup ")
+                            .append(internetEvaluation).toString());
+                    return;
+                }
+            } else {
+                if (!defaultDataPhone.isUserDataEnabled() || !backupDataPhone.isDataAllowed()) {
+                    mPhoneSwitcherCallback.onRequireImmediatelySwitchToPhone(DEFAULT_PHONE_INDEX,
+                            EVALUATION_REASON_DATA_SETTINGS_CHANGED);
+                    log(debugMessage.append(
+                            ", immediately back to default as user turns off settings").toString());
+                    return;
+                }
             }
 
             boolean backToDefault = false;
@@ -904,12 +917,15 @@
             }
 
             if (secondaryDataPhone != null) {
-                // check auto switch feature enabled
-                if (secondaryDataPhone.isDataAllowed()) {
+                // check internet data is allowed on the candidate
+                DataEvaluation internetEvaluation = secondaryDataPhone.getDataNetworkController()
+                        .getInternetEvaluation(false/*ignoreExistingNetworks*/);
+                if (!internetEvaluation.containsDisallowedReasons()) {
                     return new StabilityEventExtra(phoneId,
                             isForPerformance, mRequirePingTestBeforeSwitch);
                 } else {
-                    debugMessage.append(", but candidate's data is not allowed");
+                    debugMessage.append(", but candidate's data is not allowed ")
+                            .append(internetEvaluation);
                 }
             }
         }
@@ -921,15 +937,14 @@
      * @return {@code true} If the feature of switching base on RAT and signal strength is enabled.
      */
     private boolean isRatSignalStrengthBasedSwitchEnabled() {
-        return sFeatureFlags.autoDataSwitchRatSs() && mScoreTolerance >= 0
-                && mAutoDataSwitchPerformanceStabilityTimeThreshold >= 0;
+        return mScoreTolerance >= 0 && mAutoDataSwitchPerformanceStabilityTimeThreshold >= 0;
     }
 
     /**
      * @return {@code true} If the feature of switching to roaming non DDS is enabled.
      */
     private boolean isNddsRoamingEnabled() {
-        return sFeatureFlags.autoDataSwitchAllowRoaming() && mAllowNddsRoamning;
+        return sFeatureFlags.autoDataSwitchAllowRoaming() && mAllowNddsRoaming;
     }
 
     /**
@@ -942,39 +957,27 @@
      */
     private void startStabilityCheck(int targetPhoneId, boolean isForPerformance,
             boolean needValidation) {
-        String combinationIdentifier = targetPhoneId + "" + needValidation;
-        if (sFeatureFlags.autoDataSwitchRatSs()) {
-            StabilityEventExtra eventExtras = (StabilityEventExtra)
-                    mScheduledEventsToExtras.getOrDefault(EVENT_STABILITY_CHECK_PASSED,
-                            new StabilityEventExtra(INVALID_PHONE_INDEX, false /*need validation*/,
-                            false /*isForPerformance*/));
-            long delayMs = -1;
-            // Check if already scheduled one with that combination of extras.
-            if (eventExtras.targetPhoneId != targetPhoneId
-                    || eventExtras.needValidation != needValidation
-                    || eventExtras.isForPerformance != isForPerformance) {
-                eventExtras =
-                        new StabilityEventExtra(targetPhoneId, isForPerformance, needValidation);
+        StabilityEventExtra eventExtras = (StabilityEventExtra)
+                mScheduledEventsToExtras.getOrDefault(EVENT_STABILITY_CHECK_PASSED,
+                        new StabilityEventExtra(INVALID_PHONE_INDEX, false /*need validation*/,
+                                false /*isForPerformance*/));
+        long delayMs = -1;
+        // Check if already scheduled one with that combination of extras.
+        if (eventExtras.targetPhoneId != targetPhoneId
+                || eventExtras.needValidation != needValidation
+                || eventExtras.isForPerformance != isForPerformance) {
+            eventExtras =
+                    new StabilityEventExtra(targetPhoneId, isForPerformance, needValidation);
 
-                // Reset with new timer.
-                delayMs = isForPerformance
-                        ? mAutoDataSwitchPerformanceStabilityTimeThreshold
-                        : mAutoDataSwitchAvailabilityStabilityTimeThreshold;
-                scheduleEventWithTimer(EVENT_STABILITY_CHECK_PASSED, eventExtras, delayMs);
-            }
-            log("startStabilityCheck: "
-                    + (delayMs != -1 ? "scheduling " : "already scheduled ")
-                    + eventExtras);
-        } else if (!hasEqualMessages(EVENT_STABILITY_CHECK_PASSED, combinationIdentifier)) {
-            removeMessages(EVENT_STABILITY_CHECK_PASSED);
-            sendMessageDelayed(obtainMessage(EVENT_STABILITY_CHECK_PASSED, targetPhoneId,
-                            needValidation ? 1 : 0,
-                            combinationIdentifier),
-                    mAutoDataSwitchAvailabilityStabilityTimeThreshold);
-            log("startStabilityCheck: targetPhoneId=" + targetPhoneId
-                    + " isForPerformance=" + isForPerformance
-                    + " needValidation=" + needValidation);
+            // Reset with new timer.
+            delayMs = isForPerformance
+                    ? mAutoDataSwitchPerformanceStabilityTimeThreshold
+                    : mAutoDataSwitchAvailabilityStabilityTimeThreshold;
+            scheduleEventWithTimer(EVENT_STABILITY_CHECK_PASSED, eventExtras, delayMs);
         }
+        log("startStabilityCheck: "
+                + (delayMs != -1 ? "scheduling " : "already scheduled ")
+                + eventExtras);
     }
 
     /**
@@ -1012,19 +1015,20 @@
     }
 
     /** Auto data switch evaluation reason to string. */
-    public static @NonNull String evaluationReasonToString(
+    @NonNull
+    public static String evaluationReasonToString(
             @AutoDataSwitchEvaluationReason int reason) {
-        switch (reason) {
-            case EVALUATION_REASON_REGISTRATION_STATE_CHANGED: return "REGISTRATION_STATE_CHANGED";
-            case EVALUATION_REASON_DISPLAY_INFO_CHANGED: return "DISPLAY_INFO_CHANGED";
-            case EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED: return "SIGNAL_STRENGTH_CHANGED";
-            case EVALUATION_REASON_DEFAULT_NETWORK_CHANGED: return "DEFAULT_NETWORK_CHANGED";
-            case EVALUATION_REASON_DATA_SETTINGS_CHANGED: return "DATA_SETTINGS_CHANGED";
-            case EVALUATION_REASON_RETRY_VALIDATION: return "RETRY_VALIDATION";
-            case EVALUATION_REASON_SIM_LOADED: return "SIM_LOADED";
-            case EVALUATION_REASON_VOICE_CALL_END: return "VOICE_CALL_END";
-        }
-        return "Unknown(" + reason + ")";
+        return switch (reason) {
+            case EVALUATION_REASON_REGISTRATION_STATE_CHANGED -> "REGISTRATION_STATE_CHANGED";
+            case EVALUATION_REASON_DISPLAY_INFO_CHANGED -> "DISPLAY_INFO_CHANGED";
+            case EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED -> "SIGNAL_STRENGTH_CHANGED";
+            case EVALUATION_REASON_DEFAULT_NETWORK_CHANGED -> "DEFAULT_NETWORK_CHANGED";
+            case EVALUATION_REASON_DATA_SETTINGS_CHANGED -> "DATA_SETTINGS_CHANGED";
+            case EVALUATION_REASON_RETRY_VALIDATION -> "RETRY_VALIDATION";
+            case EVALUATION_REASON_SIM_LOADED -> "SIM_LOADED";
+            case EVALUATION_REASON_VOICE_CALL_END -> "VOICE_CALL_END";
+            default -> "Unknown(" + reason + ")";
+        };
     }
 
     /** @return {@code true} if the sub is active. */
@@ -1063,18 +1067,14 @@
     private void cancelAnyPendingSwitch() {
         mSelectedTargetPhoneId = INVALID_PHONE_INDEX;
         resetFailedCount();
-        if (sFeatureFlags.autoDataSwitchRatSs()) {
-            if (mScheduledEventsToExtras.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
-                if (mEventsToAlarmListener.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
-                    mAlarmManager.cancel(mEventsToAlarmListener.get(EVENT_STABILITY_CHECK_PASSED));
-                } else {
-                    loge("cancelAnyPendingSwitch: EVENT_STABILITY_CHECK_PASSED listener is null");
-                }
-                removeMessages(EVENT_STABILITY_CHECK_PASSED);
-                mScheduledEventsToExtras.remove(EVENT_STABILITY_CHECK_PASSED);
+        if (mScheduledEventsToExtras.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
+            if (mEventsToAlarmListener.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
+                mAlarmManager.cancel(mEventsToAlarmListener.get(EVENT_STABILITY_CHECK_PASSED));
+            } else {
+                loge("cancelAnyPendingSwitch: EVENT_STABILITY_CHECK_PASSED listener is null");
             }
-        } else {
             removeMessages(EVENT_STABILITY_CHECK_PASSED);
+            mScheduledEventsToExtras.remove(EVENT_STABILITY_CHECK_PASSED);
         }
         mPhoneSwitcherCallback.onRequireCancelAnyPendingAutoSwitchValidation();
     }
@@ -1085,8 +1085,9 @@
      * @param isDueToAutoSwitch {@code true} if the switch was due to auto data switch feature.
      */
     public void displayAutoDataSwitchNotification(int phoneId, boolean isDueToAutoSwitch) {
-        NotificationManager notificationManager = (NotificationManager)
-                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        NotificationManager notificationManager = mContext.getSystemService(
+                NotificationManager.class);
+        if (notificationManager == null) return;
         if (mDisplayedNotification) {
             // cancel posted notification if any exist
             notificationManager.cancel(AUTO_DATA_SWITCH_NOTIFICATION_TAG,
diff --git a/src/java/com/android/internal/telephony/data/CellularDataService.java b/src/java/com/android/internal/telephony/data/CellularDataService.java
index 80d6b53..a75d4df 100644
--- a/src/java/com/android/internal/telephony/data/CellularDataService.java
+++ b/src/java/com/android/internal/telephony/data/CellularDataService.java
@@ -18,6 +18,7 @@
 
 import static android.telephony.data.DataServiceCallback.RESULT_SUCCESS;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.LinkProperties;
 import android.os.AsyncResult;
@@ -41,6 +42,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * This class represents cellular data service which handles telephony data requests and response
@@ -61,6 +63,7 @@
     private static final int CANCEL_HANDOVER                        = 8;
     private static final int APN_UNTHROTTLED                        = 9;
 
+    @SuppressWarnings("unchecked")
     private class CellularDataServiceProvider extends DataService.DataServiceProvider {
 
         private final Map<Message, DataServiceCallback> mCallbackMap = new HashMap<>();
@@ -69,14 +72,15 @@
 
         private final Phone mPhone;
 
+        @SuppressWarnings("unchecked")
         private CellularDataServiceProvider(int slotId) {
             super(slotId);
 
             mPhone = PhoneFactory.getPhone(getSlotIndex());
 
-            mHandler = new Handler(Looper.myLooper()) {
+            mHandler = new Handler(Objects.requireNonNull(Looper.myLooper())) {
                 @Override
-                public void handleMessage(Message message) {
+                public void handleMessage(@NonNull Message message) {
                     DataServiceCallback callback = mCallbackMap.remove(message);
 
                     AsyncResult ar = (AsyncResult) message.obj;
@@ -147,8 +151,7 @@
             if (t == null) {
                 return RESULT_SUCCESS;
             } else {
-                if (t instanceof CommandException) {
-                    CommandException ce = (CommandException) t;
+                if (t instanceof CommandException ce) {
                     if (ce.getCommandError() == CommandException.Error.REQUEST_NOT_SUPPORTED) {
                         return DataServiceCallback.RESULT_ERROR_UNSUPPORTED;
                     } else {
@@ -163,10 +166,10 @@
         }
 
         @Override
-        public void setupDataCall(int accessNetworkType, DataProfile dataProfile,
+        public void setupDataCall(int accessNetworkType, @NonNull DataProfile dataProfile,
                 boolean isRoaming, boolean allowRoaming, int reason, LinkProperties linkProperties,
                 int pduSessionId, NetworkSliceInfo sliceInfo, TrafficDescriptor trafficDescriptor,
-                boolean matchAllRuleAllowed, DataServiceCallback callback) {
+                boolean matchAllRuleAllowed, @Nullable DataServiceCallback callback) {
             // TODO: remove isRoaming parameter
             if (DBG) log("setupDataCall " + getSlotIndex());
 
@@ -199,8 +202,8 @@
         }
 
         @Override
-        public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming,
-                DataServiceCallback callback) {
+        public void setInitialAttachApn(@NonNull DataProfile dataProfile, boolean isRoaming,
+                @Nullable DataServiceCallback callback) {
             // TODO: remove isRoaming parameter
             if (DBG) log("setInitialAttachApn " + getSlotIndex());
 
@@ -216,8 +219,8 @@
         }
 
         @Override
-        public void setDataProfile(List<DataProfile> dps, boolean isRoaming,
-                DataServiceCallback callback) {
+        public void setDataProfile(@NonNull List<DataProfile> dps, boolean isRoaming,
+                @Nullable DataServiceCallback callback) {
             // TODO: remove isRoaming parameter
             if (DBG) log("setDataProfile " + getSlotIndex());
 
@@ -229,11 +232,11 @@
                 mCallbackMap.put(message, callback);
             }
 
-            mPhone.mCi.setDataProfile(dps.toArray(new DataProfile[dps.size()]), message);
+            mPhone.mCi.setDataProfile(dps.toArray(new DataProfile[0]), message);
         }
 
         @Override
-        public void requestDataCallList(DataServiceCallback callback) {
+        public void requestDataCallList(@Nullable DataServiceCallback callback) {
             if (DBG) log("requestDataCallList " + getSlotIndex());
 
             Message message = null;
@@ -247,7 +250,7 @@
         }
 
         @Override
-        public void startHandover(int cid, DataServiceCallback callback) {
+        public void startHandover(int cid, @Nullable DataServiceCallback callback) {
             if (DBG) log("startHandover " + getSlotIndex());
             Message message = null;
             // Only obtain the message when the caller wants a callback. If the caller doesn't care
@@ -260,7 +263,7 @@
         }
 
         @Override
-        public void cancelHandover(int cid, DataServiceCallback callback) {
+        public void cancelHandover(int cid, @Nullable DataServiceCallback callback) {
             Message message = null;
             // Only obtain the message when the caller wants a callback. If the caller doesn't care
             // the request completed or results, then no need to pass the message down.
diff --git a/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java b/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java
index c1d1203..ad1a8aa 100644
--- a/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java
+++ b/src/java/com/android/internal/telephony/data/CellularNetworkValidator.java
@@ -20,6 +20,7 @@
 import static android.telephony.CarrierConfigManager.KEY_DATA_SWITCH_VALIDATION_MIN_INTERVAL_MILLIS_LONG;
 import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.Network;
@@ -58,9 +59,6 @@
  */
 public class CellularNetworkValidator {
     private static final String LOG_TAG = "NetworkValidator";
-    // If true, upon validated network cache hit, we report validationDone only when
-    // network becomes available. Otherwise, we report validationDone immediately.
-    private static boolean sWaitForNetworkAvailableWhenCacheHit = true;
 
     // States of validator. Only one validation can happen at once.
     // IDLE: no validation going on.
@@ -69,7 +67,7 @@
     private static final int STATE_VALIDATING          = 1;
     // VALIDATED: validation is done and successful.
     // Waiting for stopValidation() to release
-    // validationg NetworkRequest.
+    // validation NetworkRequest.
     private static final int STATE_VALIDATED           = 2;
 
     // Singleton instance.
@@ -79,13 +77,11 @@
 
     private int mState = STATE_IDLE;
     private int mSubId;
-    private long mTimeoutInMs;
     private boolean mReleaseAfterValidation;
 
-    private NetworkRequest mNetworkRequest;
     private ValidationCallback mValidationCallback;
-    private Context mContext;
-    private ConnectivityManager mConnectivityManager;
+    private final Context mContext;
+    private final ConnectivityManager mConnectivityManager;
     @VisibleForTesting
     public Handler mHandler = new Handler();
     @VisibleForTesting
@@ -96,18 +92,11 @@
         // A cache with fixed size. It remembers 10 most recently successfully validated networks.
         private static final int VALIDATED_NETWORK_CACHE_SIZE = 10;
         private final PriorityQueue<ValidatedNetwork> mValidatedNetworkPQ =
-                new PriorityQueue((Comparator<ValidatedNetwork>) (n1, n2) -> {
-                    if (n1.mValidationTimeStamp < n2.mValidationTimeStamp) {
-                        return -1;
-                    } else if (n1.mValidationTimeStamp > n2.mValidationTimeStamp) {
-                        return 1;
-                    } else {
-                        return 0;
-                    }
-                });
-        private final Map<String, ValidatedNetwork> mValidatedNetworkMap = new HashMap();
+                new PriorityQueue<>((Comparator<ValidatedNetwork>) Comparator.comparingLong(
+                        (ValidatedNetwork n) -> n.mValidationTimeStamp));
+        private final Map<String, ValidatedNetwork> mValidatedNetworkMap = new HashMap<>();
 
-        private final class ValidatedNetwork {
+        private static final class ValidatedNetwork {
             ValidatedNetwork(String identity, long timeStamp) {
                 mValidationIdentity = identity;
                 mValidationTimeStamp = timeStamp;
@@ -165,7 +154,6 @@
 
         private String getValidationNetworkIdentity(int subId) {
             if (!SubscriptionManager.isUsableSubscriptionId(subId)) return null;
-            if (SubscriptionManagerService.getInstance() == null) return null;
             Phone phone = PhoneFactory.getPhone(SubscriptionManagerService.getInstance()
                     .getPhoneId(subId));
             if (phone == null || phone.getServiceState() == null) return null;
@@ -270,26 +258,18 @@
             stopValidation();
         }
 
-        if (!sWaitForNetworkAvailableWhenCacheHit && mValidatedNetworkCache
-                .isRecentlyValidated(subId)) {
-            callback.onValidationDone(true, subId);
-            return;
-        }
-
         mState = STATE_VALIDATING;
         mSubId = subId;
-        mTimeoutInMs = timeoutInMs;
         mValidationCallback = callback;
         mReleaseAfterValidation = releaseAfterValidation;
-        mNetworkRequest = createNetworkRequest();
 
-        logd("Start validating subId " + mSubId + " mTimeoutInMs " + mTimeoutInMs
+        logd("Start validating subId " + mSubId + " timeoutInMs " + timeoutInMs
                 + " mReleaseAfterValidation " + mReleaseAfterValidation);
 
         mNetworkCallback = new ConnectivityNetworkCallback(subId);
 
-        mConnectivityManager.requestNetwork(mNetworkRequest, mNetworkCallback, mHandler);
-        mHandler.postDelayed(() -> onValidationTimeout(subId), mTimeoutInMs);
+        mConnectivityManager.requestNetwork(createNetworkRequest(), mNetworkCallback, mHandler);
+        mHandler.postDelayed(() -> onValidationTimeout(subId), timeoutInMs);
     }
 
     private synchronized void onValidationTimeout(int subId) {
@@ -351,7 +331,7 @@
             mState = STATE_VALIDATED;
             // If validation passed and per request to NOT release after validation, delay cleanup.
             if (!mReleaseAfterValidation && passed) {
-                mHandler.postDelayed(()-> stopValidation(), 500);
+                mHandler.postDelayed(this::stopValidation, 500);
             } else {
                 stopValidation();
             }
@@ -379,7 +359,7 @@
          * ConnectivityManager.NetworkCallback implementation
          */
         @Override
-        public void onAvailable(Network network) {
+        public void onAvailable(@NonNull Network network) {
             logd("network onAvailable " + network);
             TelephonyMetrics.getInstance().writeNetworkValidate(
                     TelephonyEvent.NetworkValidationState.NETWORK_VALIDATION_STATE_AVAILABLE);
@@ -393,7 +373,7 @@
         }
 
         @Override
-        public void onLosing(Network network, int maxMsToLive) {
+        public void onLosing(@NonNull Network network, int maxMsToLive) {
             logd("network onLosing " + network + " maxMsToLive " + maxMsToLive);
             mValidatedNetworkCache.storeLastValidationResult(
                     ConnectivityNetworkCallback.this.mSubId, false);
@@ -401,7 +381,7 @@
         }
 
         @Override
-        public void onLost(Network network) {
+        public void onLost(@NonNull Network network) {
             logd("network onLost " + network);
             mValidatedNetworkCache.storeLastValidationResult(
                     ConnectivityNetworkCallback.this.mSubId, false);
@@ -417,7 +397,7 @@
         }
 
         @Override
-        public void onCapabilitiesChanged(Network network,
+        public void onCapabilitiesChanged(@NonNull Network network,
                 NetworkCapabilities networkCapabilities) {
             if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
                 logd("onValidated");
diff --git a/src/java/com/android/internal/telephony/data/DataCallback.java b/src/java/com/android/internal/telephony/data/DataCallback.java
index 1fafe8c..05d1851 100644
--- a/src/java/com/android/internal/telephony/data/DataCallback.java
+++ b/src/java/com/android/internal/telephony/data/DataCallback.java
@@ -28,7 +28,8 @@
  */
 public class DataCallback {
     /** The executor of the callback. */
-    private final @NonNull Executor mExecutor;
+    @NonNull
+    private final Executor mExecutor;
 
     /**
      * Constructor
@@ -43,7 +44,8 @@
      * @return The executor of the callback.
      */
     @VisibleForTesting
-    public @NonNull Executor getExecutor() {
+    @NonNull
+    public Executor getExecutor() {
         return mExecutor;
     }
 
diff --git a/src/java/com/android/internal/telephony/data/DataConfigManager.java b/src/java/com/android/internal/telephony/data/DataConfigManager.java
index 0e06dad..20761e2 100644
--- a/src/java/com/android/internal/telephony/data/DataConfigManager.java
+++ b/src/java/com/android/internal/telephony/data/DataConfigManager.java
@@ -64,6 +64,7 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -163,6 +164,13 @@
     /** Network type NR_SA_MMWAVE. Should not be used outside of DataConfigManager. */
     private static final String DATA_CONFIG_NETWORK_TYPE_NR_SA_MMWAVE = "NR_SA_MMWAVE";
 
+    /**
+     * The delay in milliseconds to re-evaluate existing data networks for bootstrap sim data usage
+     * limit.
+     */
+    private static final long REEVALUATE_BOOTSTRAP_SIM_DATA_USAGE_MILLIS =
+            TimeUnit.SECONDS.toMillis(60);
+
     @StringDef(prefix = {"DATA_CONFIG_NETWORK_TYPE_"}, value = {
             DATA_CONFIG_NETWORK_TYPE_GPRS,
             DATA_CONFIG_NETWORK_TYPE_EDGE,
@@ -192,8 +200,8 @@
     private @interface DataConfigNetworkType {}
 
     /** Data config update callbacks. */
-    private final @NonNull Set<DataConfigManagerCallback> mDataConfigManagerCallbacks =
-            new ArraySet<>();
+    @NonNull
+    private final Set<DataConfigManagerCallback> mDataConfigManagerCallbacks = new ArraySet<>();
 
     /** DeviceConfig key of anomaly report threshold for back to back ims release-request. */
     private static final String KEY_ANOMALY_IMS_RELEASE_REQUEST = "anomaly_ims_release_request";
@@ -258,55 +266,77 @@
      */
     private boolean mIsApnConfigAnomalyReportEnabled;
 
-    private @NonNull final Phone mPhone;
-    private @NonNull final String mLogTag;
-
-    @NonNull private final FeatureFlags mFeatureFlags;
-    private @NonNull final CarrierConfigManager mCarrierConfigManager;
-    private @NonNull PersistableBundle mCarrierConfig = null;
-    private @NonNull Resources mResources = null;
+    @NonNull
+    private final Phone mPhone;
+    @NonNull
+    private final String mLogTag;
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
+    @NonNull
+    private final CarrierConfigManager mCarrierConfigManager;
+    @NonNull
+    private PersistableBundle mCarrierConfig = null;
+    @NonNull
+    private Resources mResources = null;
 
     /** The network capability priority map */
-    private @NonNull final Map<Integer, Integer> mNetworkCapabilityPriorityMap =
-            new ConcurrentHashMap<>();
+    @NonNull
+    private final Map<Integer, Integer> mNetworkCapabilityPriorityMap = new ConcurrentHashMap<>();
     /** The data setup retry rules */
-    private @NonNull final List<DataSetupRetryRule> mDataSetupRetryRules = new ArrayList<>();
+    @NonNull
+    private final List<DataSetupRetryRule> mDataSetupRetryRules = new ArrayList<>();
     /** The data handover retry rules */
-    private @NonNull final List<DataHandoverRetryRule> mDataHandoverRetryRules = new ArrayList<>();
+    @NonNull
+    private final List<DataHandoverRetryRule> mDataHandoverRetryRules = new ArrayList<>();
     /** The metered APN types for home network */
-    private @NonNull final @ApnType Set<Integer> mMeteredApnTypes = new HashSet<>();
+    @NonNull
+    @ApnType
+    private final Set<Integer> mMeteredApnTypes = new HashSet<>();
     /** The metered APN types for roaming network */
-    private @NonNull final @ApnType Set<Integer> mRoamingMeteredApnTypes = new HashSet<>();
+    @NonNull
+    @ApnType
+    private final Set<Integer> mRoamingMeteredApnTypes = new HashSet<>();
     /** The network types that only support single data networks */
-    private @NonNull final @NetworkType List<Integer> mSingleDataNetworkTypeList =
-            new ArrayList<>();
-    private @NonNull final @NetCapability Set<Integer> mCapabilitiesExemptFromSingleDataList =
-            new HashSet<>();
+    @NonNull
+    @NetworkType
+    private final List<Integer> mSingleDataNetworkTypeList = new ArrayList<>();
+    @NonNull
+    @NetCapability
+    private final Set<Integer> mCapabilitiesExemptFromSingleDataList = new HashSet<>();
     /** The network types that support temporarily not metered */
-    private @NonNull final @DataConfigNetworkType Set<String> mUnmeteredNetworkTypes =
-            new HashSet<>();
+    @NonNull
+    @DataConfigNetworkType
+    private final Set<String> mUnmeteredNetworkTypes = new HashSet<>();
     /** The network types that support temporarily not metered when roaming */
-    private @NonNull final @DataConfigNetworkType Set<String> mRoamingUnmeteredNetworkTypes =
-            new HashSet<>();
+    @NonNull
+    @DataConfigNetworkType
+    private final Set<String> mRoamingUnmeteredNetworkTypes = new HashSet<>();
     /** A map of network types to the downlink and uplink bandwidth values for that network type */
-    private @NonNull final @DataConfigNetworkType Map<String, DataNetwork.NetworkBandwidth>
-            mBandwidthMap = new ConcurrentHashMap<>();
-    /** A map of network types to the TCP buffer sizes for that network type */
-    private @NonNull final @DataConfigNetworkType Map<String, String> mTcpBufferSizeMap =
+    @NonNull
+    @DataConfigNetworkType
+    private final Map<String, DataNetwork.NetworkBandwidth> mBandwidthMap =
             new ConcurrentHashMap<>();
+    /** A map of network types to the TCP buffer sizes for that network type */
+    @NonNull
+    @DataConfigNetworkType
+    private final Map<String, String> mTcpBufferSizeMap = new ConcurrentHashMap<>();
     /** Rules for handover between IWLAN and cellular network. */
-    private @NonNull final List<HandoverRule> mHandoverRuleList = new ArrayList<>();
+    @NonNull
+    private final List<HandoverRule> mHandoverRuleList = new ArrayList<>();
     /** {@code True} keep IMS network in case of moving to non VOPS area; {@code false} otherwise.*/
     private boolean mShouldKeepNetworkUpInNonVops = false;
     /** The set of network types that enable VOPS even in non VOPS area. */
-    @NonNull private final @CarrierConfigManager.Ims.NetworkType List<Integer>
-            mEnabledVopsNetworkTypesInNonVops = new ArrayList<>();
+    @NonNull
+    @CarrierConfigManager.Ims.NetworkType
+    private final List<Integer> mEnabledVopsNetworkTypesInNonVops = new ArrayList<>();
     /**
      * A map of network types to the estimated downlink values by signal strength 0 - 4 for that
      * network type
      */
-    private @NonNull final @DataConfigNetworkType Map<String, int[]>
-            mAutoDataSwitchNetworkTypeSignalMap = new ConcurrentHashMap<>();
+    @NonNull
+    @DataConfigNetworkType
+    private final Map<String, int[]> mAutoDataSwitchNetworkTypeSignalMap =
+            new ConcurrentHashMap<>();
 
     /**
      * Constructor
@@ -546,14 +576,16 @@
     /**
      * @return The data setup retry rules from carrier config.
      */
-    public @NonNull List<DataSetupRetryRule> getDataSetupRetryRules() {
+    @NonNull
+    public List<DataSetupRetryRule> getDataSetupRetryRules() {
         return Collections.unmodifiableList(mDataSetupRetryRules);
     }
 
     /**
      * @return The data handover retry rules from carrier config.
      */
-    public @NonNull List<DataHandoverRetryRule> getDataHandoverRetryRules() {
+    @NonNull
+    public List<DataHandoverRetryRule> getDataHandoverRetryRules() {
         return Collections.unmodifiableList(mDataHandoverRetryRules);
     }
 
@@ -596,7 +628,9 @@
      *
      * @return The metered network capabilities when connected to a home network.
      */
-    public @NonNull @NetCapability Set<Integer> getMeteredNetworkCapabilities(boolean isRoaming) {
+    @NonNull
+    @NetCapability
+    public Set<Integer> getMeteredNetworkCapabilities(boolean isRoaming) {
         Set<Integer> meteredApnTypes = isRoaming ? mRoamingMeteredApnTypes : mMeteredApnTypes;
         Set<Integer> meteredCapabilities = meteredApnTypes.stream()
                 .map(DataUtils::apnTypeToNetworkCapability)
@@ -680,8 +714,7 @@
     /**
      * Update the voice over PS related config from the carrier config.
      */
-    private void updateVopsConfig() {
-        synchronized (this) {
+    private synchronized void updateVopsConfig() {
             mShouldKeepNetworkUpInNonVops = mCarrierConfig.getBoolean(CarrierConfigManager
                     .Ims.KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL);
             int[] allowedNetworkTypes = mCarrierConfig.getIntArray(
@@ -689,13 +722,14 @@
             if (allowedNetworkTypes != null) {
                 Arrays.stream(allowedNetworkTypes).forEach(mEnabledVopsNetworkTypesInNonVops::add);
             }
-        }
     }
 
     /**
      * @return The list of {@link NetworkType} that only supports single data networks
      */
-    public @NonNull @NetworkType List<Integer> getNetworkTypesOnlySupportSingleDataNetwork() {
+    @NonNull
+    @NetworkType
+    public List<Integer> getNetworkTypesOnlySupportSingleDataNetwork() {
         return Collections.unmodifiableList(mSingleDataNetworkTypeList);
     }
 
@@ -703,7 +737,9 @@
      * @return The list of {@link android.net.NetworkCapabilities.NetCapability} that every of which
      * is exempt from the single PDN check.
      */
-    public @NonNull @NetCapability Set<Integer> getCapabilitiesExemptFromSingleDataNetwork() {
+    @NonNull
+    @NetCapability
+    public Set<Integer> getCapabilitiesExemptFromSingleDataNetwork() {
         return Collections.unmodifiableSet(mCapabilitiesExemptFromSingleDataList);
     }
 
@@ -836,7 +872,8 @@
      * @param displayInfo The {@link TelephonyDisplayInfo} to get the bandwidth for.
      * @return The pre-configured bandwidth estimate from carrier config.
      */
-    public @NonNull DataNetwork.NetworkBandwidth getBandwidthForNetworkType(
+    @NonNull
+    public DataNetwork.NetworkBandwidth getBandwidthForNetworkType(
             @NonNull TelephonyDisplayInfo displayInfo) {
         DataNetwork.NetworkBandwidth bandwidth = mBandwidthMap.get(
                 getDataConfigNetworkType(displayInfo));
@@ -847,6 +884,14 @@
     }
 
     /**
+     * @return What kind of traffic is supported on an unrestricted satellite network.
+     */
+    @CarrierConfigManager.SATELLITE_DATA_SUPPORT_MODE
+    public int getSatelliteDataSupportMode() {
+        return mCarrierConfig.getInt(CarrierConfigManager.KEY_SATELLITE_DATA_SUPPORT_MODE_INT);
+    }
+
+    /**
      * @return Whether data throttling should be reset when the TAC changes from the carrier config.
      */
     public boolean shouldResetDataThrottlingWhenTacChanges() {
@@ -878,6 +923,21 @@
     }
 
     /**
+     * @return the interval in millisecond used to re-evaluate bootstrap sim data usage during esim
+     * bootstrap activation
+     */
+    public long getReevaluateBootstrapSimDataUsageMillis() {
+        long bootStrapSimDataUsageReevaluateInterval = mResources.getInteger(
+                com.android.internal.R.integer.config_reevaluate_bootstrap_sim_data_usage_millis);
+
+        if (bootStrapSimDataUsageReevaluateInterval <= 0) {
+            bootStrapSimDataUsageReevaluateInterval = REEVALUATE_BOOTSTRAP_SIM_DATA_USAGE_MILLIS;
+        }
+
+        return bootStrapSimDataUsageReevaluateInterval;
+    }
+
+    /**
      * Update the TCP buffer sizes from the resource overlays.
      */
     private void updateTcpBuffers() {
@@ -908,7 +968,8 @@
      * Anomaly report thresholds for frequent setup data call failure.
      * @return EventFrequency to trigger the anomaly report
      */
-    public @NonNull EventFrequency getAnomalySetupDataCallThreshold() {
+    @NonNull
+    public EventFrequency getAnomalySetupDataCallThreshold() {
         return mSetupDataCallAnomalyReportThreshold;
     }
 
@@ -917,7 +978,8 @@
      * at {@link TelephonyNetworkAgent#onNetworkUnwanted}
      * @return EventFrequency to trigger the anomaly report
      */
-    public @NonNull EventFrequency getAnomalyNetworkUnwantedThreshold() {
+    @NonNull
+    public EventFrequency getAnomalyNetworkUnwantedThreshold() {
         return mNetworkUnwantedAnomalyReportThreshold;
     }
 
@@ -925,7 +987,8 @@
      * Anomaly report thresholds for back to back release-request of IMS.
      * @return EventFrequency to trigger the anomaly report
      */
-    public @NonNull EventFrequency getAnomalyImsReleaseRequestThreshold() {
+    @NonNull
+    public EventFrequency getAnomalyImsReleaseRequestThreshold() {
         return mImsReleaseRequestAnomalyReportThreshold;
     }
 
@@ -1096,7 +1159,8 @@
      * @return The TCP configuration string for the given display info or the default value from
      *         {@code config_tcp_buffers} if unavailable.
      */
-    public @NonNull String getTcpConfigString(@NonNull TelephonyDisplayInfo displayInfo) {
+    @NonNull
+    public String getTcpConfigString(@NonNull TelephonyDisplayInfo displayInfo) {
         String config = mTcpBufferSizeMap.get(getDataConfigNetworkType(displayInfo));
         if (TextUtils.isEmpty(config)) {
             config = getDefaultTcpConfigString();
@@ -1107,7 +1171,8 @@
     /**
      * @return The fixed TCP buffer size configured based on the device's memory and performance.
      */
-    public @NonNull String getDefaultTcpConfigString() {
+    @NonNull
+    public String getDefaultTcpConfigString() {
         return mResources.getString(com.android.internal.R.string.config_tcp_buffers);
     }
 
@@ -1150,20 +1215,21 @@
     /**
      * @return The bandwidth estimation source.
      */
-    public @DataNetwork.BandwidthEstimationSource int getBandwidthEstimateSource() {
+    @DataNetwork.BandwidthEstimationSource
+    public int getBandwidthEstimateSource() {
         String source = mResources.getString(
                 com.android.internal.R.string.config_bandwidthEstimateSource);
-        switch (source) {
-            case BANDWIDTH_SOURCE_MODEM_STRING_VALUE:
-                return DataNetwork.BANDWIDTH_SOURCE_MODEM;
-            case BANDWIDTH_SOURCE_CARRIER_CONFIG_STRING_VALUE:
-                return DataNetwork.BANDWIDTH_SOURCE_CARRIER_CONFIG;
-            case BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR_STRING_VALUE:
-                return DataNetwork.BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR;
-            default:
+        return switch (source) {
+            case BANDWIDTH_SOURCE_MODEM_STRING_VALUE -> DataNetwork.BANDWIDTH_SOURCE_MODEM;
+            case BANDWIDTH_SOURCE_CARRIER_CONFIG_STRING_VALUE ->
+                    DataNetwork.BANDWIDTH_SOURCE_CARRIER_CONFIG;
+            case BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR_STRING_VALUE ->
+                    DataNetwork.BANDWIDTH_SOURCE_BANDWIDTH_ESTIMATOR;
+            default -> {
                 loge("Invalid bandwidth estimation source config: " + source);
-                return DataNetwork.BANDWIDTH_SOURCE_UNKNOWN;
-        }
+                yield DataNetwork.BANDWIDTH_SOURCE_UNKNOWN;
+            }
+        };
     }
 
     /**
@@ -1172,8 +1238,9 @@
      * @param displayInfo The {@link TelephonyDisplayInfo} used to determine the type.
      * @return The equivalent {@link DataConfigNetworkType}.
      */
-    private static @NonNull @DataConfigNetworkType String getDataConfigNetworkType(
-            @NonNull TelephonyDisplayInfo displayInfo) {
+    @NonNull
+    @DataConfigNetworkType
+    private static String getDataConfigNetworkType(@NonNull TelephonyDisplayInfo displayInfo) {
         int networkType = displayInfo.getNetworkType();
         switch (displayInfo.getOverrideNetworkType()) {
             case TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED:
@@ -1282,7 +1349,8 @@
      *
      * @see CarrierConfigManager#KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY
      */
-    public @NonNull List<HandoverRule> getHandoverRules() {
+    @NonNull
+    public List<HandoverRule> getHandoverRules() {
         return Collections.unmodifiableList(mHandoverRuleList);
     }
 
@@ -1300,52 +1368,33 @@
      * @param networkType The network type
      * @return The equivalent data config network type
      */
-    private static @NonNull @DataConfigNetworkType String networkTypeToDataConfigNetworkType(
+    @NonNull
+    @DataConfigNetworkType
+    private static String networkTypeToDataConfigNetworkType(
             @NetworkType int networkType) {
-        switch (networkType) {
-            case TelephonyManager.NETWORK_TYPE_GPRS:
-                return DATA_CONFIG_NETWORK_TYPE_GPRS;
-            case TelephonyManager.NETWORK_TYPE_EDGE:
-                return DATA_CONFIG_NETWORK_TYPE_EDGE;
-            case TelephonyManager.NETWORK_TYPE_UMTS:
-                return DATA_CONFIG_NETWORK_TYPE_UMTS;
-            case TelephonyManager.NETWORK_TYPE_HSDPA:
-                return DATA_CONFIG_NETWORK_TYPE_HSDPA;
-            case TelephonyManager.NETWORK_TYPE_HSUPA:
-                return DATA_CONFIG_NETWORK_TYPE_HSUPA;
-            case TelephonyManager.NETWORK_TYPE_HSPA:
-                return DATA_CONFIG_NETWORK_TYPE_HSPA;
-            case TelephonyManager.NETWORK_TYPE_CDMA:
-                return DATA_CONFIG_NETWORK_TYPE_CDMA;
-            case TelephonyManager.NETWORK_TYPE_EVDO_0:
-                return DATA_CONFIG_NETWORK_TYPE_EVDO_0;
-            case TelephonyManager.NETWORK_TYPE_EVDO_A:
-                return DATA_CONFIG_NETWORK_TYPE_EVDO_A;
-            case TelephonyManager.NETWORK_TYPE_EVDO_B:
-                return DATA_CONFIG_NETWORK_TYPE_EVDO_B;
-            case TelephonyManager.NETWORK_TYPE_1xRTT:
-                return DATA_CONFIG_NETWORK_TYPE_1xRTT;
-            case TelephonyManager.NETWORK_TYPE_LTE:
-                return DATA_CONFIG_NETWORK_TYPE_LTE;
-            case TelephonyManager.NETWORK_TYPE_EHRPD:
-                return DATA_CONFIG_NETWORK_TYPE_EHRPD;
-            case TelephonyManager.NETWORK_TYPE_IDEN:
-                return DATA_CONFIG_NETWORK_TYPE_IDEN;
-            case TelephonyManager.NETWORK_TYPE_HSPAP:
-                return DATA_CONFIG_NETWORK_TYPE_HSPAP;
-            case TelephonyManager.NETWORK_TYPE_GSM:
-                return DATA_CONFIG_NETWORK_TYPE_GSM;
-            case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
-                return DATA_CONFIG_NETWORK_TYPE_TD_SCDMA;
-            case TelephonyManager.NETWORK_TYPE_IWLAN:
-                return DATA_CONFIG_NETWORK_TYPE_IWLAN;
-            case TelephonyManager.NETWORK_TYPE_LTE_CA:
-                return DATA_CONFIG_NETWORK_TYPE_LTE_CA;
-            case TelephonyManager.NETWORK_TYPE_NR:
-                return DATA_CONFIG_NETWORK_TYPE_NR_SA;
-            default:
-                return "";
-        }
+        return switch (networkType) {
+            case TelephonyManager.NETWORK_TYPE_GPRS -> DATA_CONFIG_NETWORK_TYPE_GPRS;
+            case TelephonyManager.NETWORK_TYPE_EDGE -> DATA_CONFIG_NETWORK_TYPE_EDGE;
+            case TelephonyManager.NETWORK_TYPE_UMTS -> DATA_CONFIG_NETWORK_TYPE_UMTS;
+            case TelephonyManager.NETWORK_TYPE_HSDPA -> DATA_CONFIG_NETWORK_TYPE_HSDPA;
+            case TelephonyManager.NETWORK_TYPE_HSUPA -> DATA_CONFIG_NETWORK_TYPE_HSUPA;
+            case TelephonyManager.NETWORK_TYPE_HSPA -> DATA_CONFIG_NETWORK_TYPE_HSPA;
+            case TelephonyManager.NETWORK_TYPE_CDMA -> DATA_CONFIG_NETWORK_TYPE_CDMA;
+            case TelephonyManager.NETWORK_TYPE_EVDO_0 -> DATA_CONFIG_NETWORK_TYPE_EVDO_0;
+            case TelephonyManager.NETWORK_TYPE_EVDO_A -> DATA_CONFIG_NETWORK_TYPE_EVDO_A;
+            case TelephonyManager.NETWORK_TYPE_EVDO_B -> DATA_CONFIG_NETWORK_TYPE_EVDO_B;
+            case TelephonyManager.NETWORK_TYPE_1xRTT -> DATA_CONFIG_NETWORK_TYPE_1xRTT;
+            case TelephonyManager.NETWORK_TYPE_LTE -> DATA_CONFIG_NETWORK_TYPE_LTE;
+            case TelephonyManager.NETWORK_TYPE_EHRPD -> DATA_CONFIG_NETWORK_TYPE_EHRPD;
+            case TelephonyManager.NETWORK_TYPE_IDEN -> DATA_CONFIG_NETWORK_TYPE_IDEN;
+            case TelephonyManager.NETWORK_TYPE_HSPAP -> DATA_CONFIG_NETWORK_TYPE_HSPAP;
+            case TelephonyManager.NETWORK_TYPE_GSM -> DATA_CONFIG_NETWORK_TYPE_GSM;
+            case TelephonyManager.NETWORK_TYPE_TD_SCDMA -> DATA_CONFIG_NETWORK_TYPE_TD_SCDMA;
+            case TelephonyManager.NETWORK_TYPE_IWLAN -> DATA_CONFIG_NETWORK_TYPE_IWLAN;
+            case TelephonyManager.NETWORK_TYPE_LTE_CA -> DATA_CONFIG_NETWORK_TYPE_LTE_CA;
+            case TelephonyManager.NETWORK_TYPE_NR -> DATA_CONFIG_NETWORK_TYPE_NR_SA;
+            default -> "";
+        };
     }
 
     /**
@@ -1353,7 +1402,8 @@
      *
      * @see CarrierConfigManager#KEY_DATA_STALL_RECOVERY_TIMERS_LONG_ARRAY
      */
-    public @NonNull long[] getDataStallRecoveryDelayMillis() {
+    @NonNull
+    public long[] getDataStallRecoveryDelayMillis() {
         return mCarrierConfig.getLongArray(
             CarrierConfigManager.KEY_DATA_STALL_RECOVERY_TIMERS_LONG_ARRAY);
     }
@@ -1363,7 +1413,8 @@
      *
      * @see CarrierConfigManager#KEY_DATA_STALL_RECOVERY_SHOULD_SKIP_BOOL_ARRAY
      */
-    public @NonNull boolean[] getDataStallRecoveryShouldSkipArray() {
+    @NonNull
+    public boolean[] getDataStallRecoveryShouldSkipArray() {
         return mCarrierConfig.getBooleanArray(
             CarrierConfigManager.KEY_DATA_STALL_RECOVERY_SHOULD_SKIP_BOOL_ARRAY);
     }
@@ -1372,7 +1423,8 @@
      * @return The default preferred APN. An empty string if not configured. This is used for the
      * first time boot up where preferred APN is not set.
      */
-    public @NonNull String getDefaultPreferredApn() {
+    @NonNull
+    public String getDefaultPreferredApn() {
         return TextUtils.emptyIfNull(mCarrierConfig.getString(
                 CarrierConfigManager.KEY_DEFAULT_PREFERRED_APN_NAME_STRING));
     }
@@ -1391,7 +1443,9 @@
      * cases that we need to use "user-added" APN for initial attach. The regular way to configure
      * IA APN is by adding "IA" type to the APN in APN config.
      */
-    public @NonNull @ApnType List<Integer> getAllowedInitialAttachApnTypes() {
+    @NonNull
+    @ApnType
+    public List<Integer> getAllowedInitialAttachApnTypes() {
         String[] apnTypesArray = mCarrierConfig.getStringArray(
                 CarrierConfigManager.KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY);
         if (apnTypesArray != null) {
@@ -1436,6 +1490,19 @@
     }
 
     /**
+     * @return The capabilities that network will be forced to mark as cellular transport.
+     */
+    @NetCapability
+    public Set<Integer> getForcedCellularTransportCapabilities() {
+        String[] forcedCellularTransportCapabilities = mResources.getStringArray(
+                com.android.internal.R.array.config_force_cellular_transport_capabilities);
+
+        return Arrays.stream(forcedCellularTransportCapabilities)
+                .map(DataUtils::getNetworkCapabilityFromString)
+                .collect(Collectors.toSet());
+    }
+
+    /**
      * Log debug messages.
      * @param s debug messages
      */
@@ -1543,6 +1610,9 @@
         pw.println("isTetheringProfileDisabledForRoaming="
                 + isTetheringProfileDisabledForRoaming());
         pw.println("allowClearInitialAttachDataProfile=" + allowClearInitialAttachDataProfile());
+        pw.println("forcedCellularTransportCapabilities=" + getForcedCellularTransportCapabilities()
+                .stream().map(DataUtils::networkCapabilityToString)
+                .collect(Collectors.joining(",")));
         pw.decreaseIndent();
     }
 }
diff --git a/src/java/com/android/internal/telephony/data/DataEvaluation.java b/src/java/com/android/internal/telephony/data/DataEvaluation.java
index 3d10e9c..f5eae91 100644
--- a/src/java/com/android/internal/telephony/data/DataEvaluation.java
+++ b/src/java/com/android/internal/telephony/data/DataEvaluation.java
@@ -36,25 +36,30 @@
  */
 public class DataEvaluation {
     /** The reason for this evaluation */
-    private final @NonNull DataEvaluationReason mDataEvaluationReason;
+    @NonNull
+    private final DataEvaluationReason mDataEvaluationReason;
 
     /** Data disallowed reasons. There could be multiple reasons for not allowing data. */
-    private final @NonNull Set<DataDisallowedReason> mDataDisallowedReasons = new HashSet<>();
+    @NonNull
+    private final Set<DataDisallowedReason> mDataDisallowedReasons = new HashSet<>();
 
     /** Data allowed reason. It is intended to only have one allowed reason. */
-    private @NonNull DataAllowedReason mDataAllowedReason = DataAllowedReason.NONE;
+    @NonNull
+    private DataAllowedReason mDataAllowedReason = DataAllowedReason.NONE;
 
-    private @Nullable DataProfile mCandidateDataProfile = null;
+    @Nullable
+    private DataProfile mCandidateDataProfile = null;
 
     /** The timestamp of evaluation time */
-    private @CurrentTimeMillisLong long mEvaluatedTime = 0;
+    @CurrentTimeMillisLong
+    private long mEvaluatedTime = 0;
 
     /**
      * Constructor
      *
      * @param reason The reason for this evaluation.
      */
-    public DataEvaluation(DataEvaluationReason reason) {
+    public DataEvaluation(@NonNull DataEvaluationReason reason) {
         mDataEvaluationReason = reason;
     }
 
@@ -100,14 +105,16 @@
     /**
      * @return List of data disallowed reasons.
      */
-    public @NonNull List<DataDisallowedReason> getDataDisallowedReasons() {
+    @NonNull
+    public List<DataDisallowedReason> getDataDisallowedReasons() {
         return new ArrayList<>(mDataDisallowedReasons);
     }
 
     /**
      * @return The data allowed reason.
      */
-    public @NonNull DataAllowedReason getDataAllowedReason() {
+    @NonNull
+    public DataAllowedReason getDataAllowedReason() {
         return mDataAllowedReason;
     }
 
@@ -123,7 +130,8 @@
     /**
      * @return The candidate data profile for setup data network.
      */
-    public @Nullable DataProfile getCandidateDataProfile() {
+    @Nullable
+    public DataProfile getCandidateDataProfile() {
         return mCandidateDataProfile;
     }
 
@@ -131,7 +139,7 @@
      * @return {@code true} if the evaluation contains disallowed reasons.
      */
     public boolean containsDisallowedReasons() {
-        return mDataDisallowedReasons.size() != 0;
+        return !mDataDisallowedReasons.isEmpty();
     }
 
     /**
@@ -346,7 +354,9 @@
         /** Handover max retry stopped but network is not on the preferred transport. */
         HANDOVER_RETRY_STOPPED(true),
         /** BootStrap sim data limit reached. */
-        DATA_LIMIT_REACHED(true);
+        DATA_LIMIT_REACHED(true),
+        /** Data network connectivity transport not allowed. */
+        DATA_NETWORK_TRANSPORT_NOT_ALLOWED(true);
 
         private final boolean mIsHardReason;
 
@@ -415,8 +425,9 @@
     @Override
     public String toString() {
         StringBuilder evaluationStr = new StringBuilder();
-        evaluationStr.append("Data evaluation: evaluation reason:" + mDataEvaluationReason + ", ");
-        if (mDataDisallowedReasons.size() > 0) {
+        evaluationStr.append("Data evaluation: evaluation reason:")
+                .append(mDataEvaluationReason).append(", ");
+        if (!mDataDisallowedReasons.isEmpty()) {
             evaluationStr.append("Data disallowed reasons:");
             for (DataDisallowedReason reason : mDataDisallowedReasons) {
                 evaluationStr.append(" ").append(reason);
@@ -425,8 +436,8 @@
             evaluationStr.append("Data allowed reason:");
             evaluationStr.append(" ").append(mDataAllowedReason);
         }
-        evaluationStr.append(", candidate profile=" + mCandidateDataProfile);
-        evaluationStr.append(", time=" + DataUtils.systemTimeToString(mEvaluatedTime));
+        evaluationStr.append(", candidate profile=").append(mCandidateDataProfile);
+        evaluationStr.append(", time=").append(DataUtils.systemTimeToString(mEvaluatedTime));
         return evaluationStr.toString();
     }
 
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index 0dbbc5c..8369874 100644
--- a/src/java/com/android/internal/telephony/data/DataNetwork.java
+++ b/src/java/com/android/internal/telephony/data/DataNetwork.java
@@ -103,6 +103,7 @@
 import com.android.internal.telephony.data.TelephonyNetworkAgent.TelephonyNetworkAgentCallback;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.DataCallSessionStats;
+import com.android.internal.telephony.metrics.DataNetworkValidationStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FunctionalUtils;
@@ -124,6 +125,7 @@
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -135,11 +137,11 @@
 
 /**
  * DataNetwork class represents a single PDN (Packet Data Network).
- *
+ * <p>
  * The life cycle of a data network starts from {@link ConnectingState}. If setup data request
  * succeeds, then it enters {@link ConnectedState}, otherwise it enters
  * {@link DisconnectedState}.
- *
+ * <p>
  * When data network is in {@link ConnectingState}, it can enter {@link HandoverState} if handover
  * between IWLAN and cellular occurs. After handover completes or fails, it return back to
  * {@link ConnectedState}. When the data network is about to be disconnected, it first enters
@@ -148,9 +150,9 @@
  * notifies data disconnected. Note that an unsolicited disconnected event from {@link DataService}
  * or any vendor HAL failure response can immediately move data network from {@link ConnectedState}
  * to {@link DisconnectedState}. {@link DisconnectedState} is the final state of a data network.
- *
+ * <p>
  * State machine diagram:
- *
+ * <p>
  *
  *                                  ┌─────────┐
  *                                  │Handover │
@@ -200,6 +202,9 @@
     /** Event for bandwidth estimation from the modem changed. */
     private static final int EVENT_BANDWIDTH_ESTIMATE_FROM_MODEM_CHANGED = 11;
 
+    /** Event to report anomaly {@link #EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE} not received. */
+    private static final int EVENT_CANCEL_HANDOVER_NO_RESPONSE = 12;
+
     /** Event for display info changed. This is for getting 5G NSA or mmwave information. */
     private static final int EVENT_DISPLAY_INFO_CHANGED = 13;
 
@@ -269,17 +274,6 @@
     /** Invalid context id. */
     private static final int INVALID_CID = -1;
 
-    /**
-     * The data network providing default internet will have a higher score of 50. Other network
-     * will have a slightly lower score of 45. The intention is other connections will not cause
-     * connectivity service to tear down default internet connection. For example, to validate
-     * internet connection on non-default data SIM, we'll set up a temporary internet network on
-     * that data SIM. In this case, score of 45 is assigned so connectivity service will not replace
-     * the default internet network with it.
-     */
-    private static final int DEFAULT_INTERNET_NETWORK_SCORE = 50;
-    private static final int OTHER_NETWORK_SCORE = 45;
-
     @IntDef(prefix = {"TEAR_DOWN_REASON_"},
             value = {
                     TEAR_DOWN_REASON_NONE,
@@ -314,6 +308,7 @@
                     TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK,
                     TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED,
                     TEAR_DOWN_REASON_DATA_LIMIT_REACHED,
+                    TEAR_DOWN_REASON_DATA_NETWORK_TRANSPORT_NOT_ALLOWED,
             })
     public @interface TearDownReason {}
 
@@ -413,6 +408,9 @@
     /** Data network tear down due to bootstrap sim data limit reached. */
     public static final int TEAR_DOWN_REASON_DATA_LIMIT_REACHED = 31;
 
+    /** Data network tear down due to current data network transport mismatch. */
+    public static final int TEAR_DOWN_REASON_DATA_NETWORK_TRANSPORT_NOT_ALLOWED = 32;
+
     //********************************************************************************************//
     // WHENEVER ADD A NEW TEAR DOWN REASON, PLEASE UPDATE DataDeactivateReasonEnum in enums.proto //
     //********************************************************************************************//
@@ -507,10 +505,12 @@
     private final DisconnectedState mDisconnectedState = new DisconnectedState();
 
     /** The phone instance. */
-    private final @NonNull Phone mPhone;
+    @NonNull
+    private final Phone mPhone;
 
     /** Feature flags */
-    private final @NonNull FeatureFlags mFlags;
+    @NonNull
+    private final FeatureFlags mFlags;
 
     /**
      * The subscription id. This is assigned when the network is created, and not supposed to
@@ -519,7 +519,8 @@
     private final int mSubId;
 
     /** The network score of this network. */
-    private @NonNull NetworkScore mNetworkScore;
+    @NonNull
+    private NetworkScore mNetworkScore;
 
     /**
      * Indicates that
@@ -534,13 +535,15 @@
     private boolean mEverConnected = false;
 
     /** RIL interface. */
-    private final @NonNull CommandsInterface mRil;
+    @NonNull
+    private final CommandsInterface mRil;
 
     /** Local log. */
     private final LocalLog mLocalLog = new LocalLog(128);
 
     /** The callback to receives data network state update. */
-    private final @NonNull DataNetworkCallback mDataNetworkCallback;
+    @NonNull
+    private final DataNetworkCallback mDataNetworkCallback;
 
     /** The log tag. */
     private String mLogTag;
@@ -548,6 +551,10 @@
     /** Metrics of per data network connection. */
     private final DataCallSessionStats mDataCallSessionStats;
 
+    /** Metrics of per data network validation. */
+    @NonNull
+    private final DataNetworkValidationStats mDataNetworkValidationStats;
+
     /**
      * The unique context id assigned by the data service in {@link DataCallResponse#getId()}. One
      * for {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN} and one for
@@ -570,71 +577,92 @@
      * Data service managers for accessing {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN} and
      * {@link AccessNetworkConstants#TRANSPORT_TYPE_WLAN} data services.
      */
-    private final @NonNull SparseArray<DataServiceManager> mDataServiceManagers;
+    @NonNull
+    private final SparseArray<DataServiceManager> mDataServiceManagers;
 
     /** Access networks manager. */
-    private final @NonNull AccessNetworksManager mAccessNetworksManager;
+    @NonNull
+    private final AccessNetworksManager mAccessNetworksManager;
 
     /** Data network controller. */
-    private final @NonNull DataNetworkController mDataNetworkController;
+    @NonNull
+    private final DataNetworkController mDataNetworkController;
 
     /** Data network controller callback. */
-    private final @NonNull DataNetworkController.DataNetworkControllerCallback
+    @NonNull
+    private final DataNetworkController.DataNetworkControllerCallback
             mDataNetworkControllerCallback;
 
     /** Data settings manager callback. */
-    private @NonNull DataSettingsManagerCallback mDataSettingsManagerCallback;
+    @NonNull
+    private DataSettingsManagerCallback mDataSettingsManagerCallback;
 
     /** Data config manager. */
-    private final @NonNull DataConfigManager mDataConfigManager;
+    @NonNull
+    private final DataConfigManager mDataConfigManager;
 
     /** VCN manager. */
-    private final @Nullable VcnManager mVcnManager;
+    @Nullable
+    private final VcnManager mVcnManager;
 
     /** VCN policy changed listener. */
-    private @Nullable VcnNetworkPolicyChangeListener mVcnPolicyChangeListener;
+    @Nullable
+    private VcnNetworkPolicyChangeListener mVcnPolicyChangeListener;
 
     /** The network agent associated with this data network. */
-    private @NonNull TelephonyNetworkAgent mNetworkAgent;
+    @NonNull
+    private TelephonyNetworkAgent mNetworkAgent;
 
     /** QOS callback tracker. This is only created after network connected on WWAN. */
-    private @Nullable QosCallbackTracker mQosCallbackTracker;
+    @Nullable
+    private QosCallbackTracker mQosCallbackTracker;
 
     /** NAT keepalive tracker. */
-    private @Nullable KeepaliveTracker mKeepaliveTracker;
+    @Nullable
+    private KeepaliveTracker mKeepaliveTracker;
 
     /** The data profile used to establish this data network. */
-    private @NonNull DataProfile mDataProfile;
+    @NonNull
+    private DataProfile mDataProfile;
 
     /**
      * The data profile used for data handover. Some carriers might use different data profile
      * between IWLAN and cellular. Only set before handover started.
      */
-    private @Nullable DataProfile mHandoverDataProfile;
+    @Nullable
+    private DataProfile mHandoverDataProfile;
 
     /** The network capabilities of this data network. */
-    private @NonNull NetworkCapabilities mNetworkCapabilities;
+    @NonNull
+    private NetworkCapabilities mNetworkCapabilities;
 
     /** The matched traffic descriptor returned from setup data call request. */
-    private final @NonNull List<TrafficDescriptor> mTrafficDescriptors = new ArrayList<>();
+    @NonNull
+    private final List<TrafficDescriptor> mTrafficDescriptors = new ArrayList<>();
 
     /** The link properties of this data network. */
-    private @NonNull LinkProperties mLinkProperties;
+    @NonNull
+    private LinkProperties mLinkProperties;
 
     /** The network slice info. */
-    private @Nullable NetworkSliceInfo mNetworkSliceInfo;
+    @Nullable
+    private NetworkSliceInfo mNetworkSliceInfo;
 
     /** The link status (i.e. RRC state). */
-    private @LinkStatus int mLinkStatus = DataCallResponse.LINK_STATUS_UNKNOWN;
+    @LinkStatus
+    private int mLinkStatus = DataCallResponse.LINK_STATUS_UNKNOWN;
 
     /** The network bandwidth. */
-    private @NonNull NetworkBandwidth mNetworkBandwidth = new NetworkBandwidth(14, 14);
+    @NonNull
+    private NetworkBandwidth mNetworkBandwidth = new NetworkBandwidth(14, 14);
 
     /** The TCP buffer sizes config. */
-    private @NonNull String mTcpBufferSizes;
+    @NonNull
+    private String mTcpBufferSizes;
 
     /** The telephony display info. */
-    private @NonNull TelephonyDisplayInfo mTelephonyDisplayInfo;
+    @NonNull
+    private TelephonyDisplayInfo mTelephonyDisplayInfo;
 
     /** Whether {@link NetworkCapabilities#NET_CAPABILITY_TEMPORARILY_NOT_METERED} is supported. */
     private boolean mTempNotMeteredSupported = false;
@@ -646,7 +674,8 @@
     private boolean mCongested = false;
 
     /** The network requests associated with this data network */
-    private final @NonNull NetworkRequestList mAttachedNetworkRequestList =
+    @NonNull
+    private final NetworkRequestList mAttachedNetworkRequestList =
             new NetworkRequestList();
 
     /**
@@ -655,18 +684,21 @@
      * {@link DataServiceCallback#onDataCallListChanged(List)}. The very first update must be
      * from {@link DataServiceCallback#onSetupDataCallComplete(int, DataCallResponse)}.
      */
-    private @Nullable DataCallResponse mDataCallResponse = null;
+    @Nullable
+    private DataCallResponse mDataCallResponse = null;
 
     /**
      * The fail cause from either setup data failure or unsolicited disconnect reported by data
      * service.
      */
-    private @DataFailureCause int mFailCause = DataFailCause.NONE;
+    @DataFailureCause
+    private int mFailCause = DataFailCause.NONE;
 
     /**
      * The tear down reason if the data call is voluntarily deactivated, not due to failure.
      */
-    private @TearDownReason int mTearDownReason = TEAR_DOWN_REASON_NONE;
+    @TearDownReason
+    private int mTearDownReason = TEAR_DOWN_REASON_NONE;
 
     /**
      * The retry delay in milliseconds from setup data failure.
@@ -686,40 +718,53 @@
      * The current transport of the data network. For handover, the current transport will be set
      * after handover completes.
      */
-    private @TransportType int mTransport;
+    @TransportType
+    private int mTransport;
 
     /**
      * The last known data network type.
      */
-    private @NetworkType int mLastKnownDataNetworkType;
+    @NetworkType
+    private int mLastKnownDataNetworkType;
 
     /**
      * The last known roaming state of this data network.
      */
     private boolean mLastKnownRoamingState;
 
+    /**
+     * The non-terrestrial status
+     */
+    private final boolean mIsSatellite;
+
     /** The reason that why setting up this data network is allowed. */
-    private @NonNull DataAllowedReason mDataAllowedReason;
+    @NonNull
+    private final DataAllowedReason mDataAllowedReason;
 
     /**
      * PCO (Protocol Configuration Options) data received from the network. The first key is the
      * cid of the PCO data, the second key is the PCO id, the value is the PCO data.
      */
-    private final @NonNull Map<Integer, Map<Integer, PcoData>> mPcoData = new ArrayMap<>();
+    @NonNull
+    private final Map<Integer, Map<Integer, PcoData>> mPcoData = new ArrayMap<>();
 
     /** The QOS bearer sessions. */
-    private final @NonNull List<QosBearerSession> mQosBearerSessions = new ArrayList<>();
+    @NonNull
+    private final List<QosBearerSession> mQosBearerSessions = new ArrayList<>();
 
     /** The QOS for the Default Bearer, should be non-null on LTE and NR */
-    private @Nullable Qos mDefaultQos;
+    @Nullable
+    private Qos mDefaultQos;
 
     /**
      * The UIDs of packages that have carrier privilege.
      */
-    private @NonNull int[] mAdministratorUids = new int[0];
+    @NonNull
+    private int[] mAdministratorUids = new int[0];
 
     /** Carrier privileges callback to monitor administrator UID change. */
-    private @Nullable TelephonyManager.CarrierPrivilegesCallback mCarrierPrivilegesCallback;
+    @Nullable
+    private TelephonyManager.CarrierPrivilegesCallback mCarrierPrivilegesCallback;
 
     /**
      * Carrier service package uid. This UID will not change through the life cycle of data network.
@@ -729,30 +774,42 @@
     /**
      * Link bandwidth estimator callback for receiving latest link bandwidth information.
      */
-    private @Nullable LinkBandwidthEstimatorCallback mLinkBandwidthEstimatorCallback;
+    @Nullable
+    private LinkBandwidthEstimatorCallback mLinkBandwidthEstimatorCallback;
 
     /**
      * Data config callback for carrier config update.
      */
-    private @Nullable DataConfigManagerCallback mDataConfigManagerCallback;
+    @Nullable
+    private DataConfigManagerCallback mDataConfigManagerCallback;
 
     /**
      * Network validation status for this data network. If the data service provider does not
      * support the network validation feature, should be UNSUPPORTED.
      */
-    private @PreciseDataConnectionState.NetworkValidationStatus int mNetworkValidationStatus =
+    @PreciseDataConnectionState.NetworkValidationStatus
+    private int mNetworkValidationStatus =
             PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED;
 
     /**
      * Callback used to respond to a network validation request to determine whether the request is
      * successfully submitted. If the request has been submitted, change it to null.
      */
-    private @Nullable Consumer<Integer> mNetworkValidationResultCodeCallback;
+    @Nullable
+    private Consumer<Integer> mNetworkValidationResultCodeCallback;
 
     /**
      * Callback used to listen QNS preference changes.
      */
-    private @Nullable AccessNetworksManagerCallback mAccessNetworksManagerCallback;
+    @Nullable
+    private AccessNetworksManagerCallback mAccessNetworksManagerCallback;
+
+    /**
+     * PreciseDataConnectionState, the most recently notified. If it has never been notified, it is
+     * null.
+     */
+    @Nullable
+    private PreciseDataConnectionState mPreciseDataConnectionState;
 
     /**
      * The network bandwidth.
@@ -977,6 +1034,7 @@
                 mDataNetworkControllerCallback);
         mDataConfigManager = mDataNetworkController.getDataConfigManager();
         mDataCallSessionStats = new DataCallSessionStats(mPhone);
+        mDataNetworkValidationStats = new DataNetworkValidationStats(mPhone);
         mDataNetworkCallback = callback;
         mDataProfile = dataProfile;
         if (dataProfile.getTrafficDescriptor() != null) {
@@ -988,25 +1046,40 @@
         mTransport = transport;
         mLastKnownDataNetworkType = getDataNetworkType();
         mLastKnownRoamingState = mPhone.getServiceState().getDataRoamingFromRegistration();
+        mIsSatellite = mPhone.getServiceState().isUsingNonTerrestrialNetwork()
+                && transport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
         mDataAllowedReason = dataAllowedReason;
         dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime());
-        mAttachedNetworkRequestList.addAll(networkRequestList);
         for (int transportType : mAccessNetworksManager.getAvailableTransports()) {
             mCid.put(transportType, INVALID_CID);
         }
         mTelephonyDisplayInfo = mPhone.getDisplayInfoController().getTelephonyDisplayInfo();
         mTcpBufferSizes = mDataConfigManager.getTcpConfigString(mTelephonyDisplayInfo);
 
-        for (TelephonyNetworkRequest networkRequest : networkRequestList) {
-            networkRequest.setAttachedNetwork(DataNetwork.this);
-            networkRequest.setState(TelephonyNetworkRequest.REQUEST_STATE_SATISFIED);
-        }
-
+        // network capabilities infer connectivity transport and MMTEL from the requested
+        // capabilities.
+        // TODO: Ideally we shouldn't infer network capabilities base on the requested capabilities,
+        // but currently there are 2 hacks associated with getForcedCellularTransportCapabilities
+        // and IMS service requesting IMS|MMTEL that need to support. When we stop supporting these
+        // cases, we shouldn't consider the requests when determining the network capabilities.
+        mAttachedNetworkRequestList.addAll(networkRequestList);
         // Update the capabilities in the constructor is to make sure the data network has initial
         // capability immediately after created. Doing this connecting state creates the window that
         // DataNetworkController might check if existing data network's capability can satisfy the
         // next network request within this window.
         updateNetworkCapabilities();
+
+        // Remove the requests that can't use the initial capabilities
+        ListIterator<TelephonyNetworkRequest> iter = mAttachedNetworkRequestList.listIterator();
+        while (iter.hasNext()) {
+            TelephonyNetworkRequest request = iter.next();
+            if (request.canBeSatisfiedBy(mNetworkCapabilities)) {
+                request.setAttachedNetwork(DataNetwork.this);
+                request.setState(TelephonyNetworkRequest.REQUEST_STATE_SATISFIED);
+            } else {
+                iter.remove();
+            }
+        }
     }
 
     /**
@@ -1050,7 +1123,8 @@
      *
      * @return The telephony network agent.
      */
-    private @NonNull TelephonyNetworkAgent createNetworkAgent() {
+    @NonNull
+    private TelephonyNetworkAgent createNetworkAgent() {
         final NetworkAgentConfig.Builder configBuilder = new NetworkAgentConfig.Builder();
         configBuilder.setLegacyType(ConnectivityManager.TYPE_MOBILE);
         configBuilder.setLegacyTypeName("MOBILE");
@@ -1083,13 +1157,8 @@
                 mPhone.getPhoneId());
         final NetworkProvider provider = (null == factory) ? null : factory.getProvider();
 
-        // Always prefer IWLAN network for MMS designated network.
-        // TODO(b/293656884) Proper use of primary transport to avoid conflicting with DSDA.
-        boolean isPreferred = mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
-                && getApnTypeNetworkCapability() == NetworkCapabilities.NET_CAPABILITY_MMS;
-
-        mNetworkScore = new NetworkScore.Builder().setTransportPrimary(isPreferred)
-                .setKeepConnectedReason(isHandoverInProgress()
+        mNetworkScore = new NetworkScore.Builder()
+               .setKeepConnectedReason(isHandoverInProgress()
                         ? NetworkScore.KEEP_CONNECTED_FOR_HANDOVER
                         : NetworkScore.KEEP_CONNECTED_NONE).build();
 
@@ -1164,7 +1233,7 @@
 
             mCarrierPrivilegesCallback =
                     (Set<String> privilegedPackageNames, Set<Integer> privilegedUids) -> {
-                        log("onCarrierPrivilegesChanged, Uids=" + privilegedUids.toString());
+                        log("onCarrierPrivilegesChanged, Uids=" + privilegedUids);
                         Message message = obtainMessage(EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED);
                         AsyncResult.forMessage(
                                 message,
@@ -1316,11 +1385,13 @@
                     break;
                 }
                 case EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE:
+                    removeMessages(EVENT_CANCEL_HANDOVER_NO_RESPONSE);
                     log("Notified handover cancelled.");
                     break;
                 case EVENT_BANDWIDTH_ESTIMATE_FROM_MODEM_CHANGED:
                 case EVENT_TEAR_DOWN_NETWORK:
                 case EVENT_STUCK_IN_TRANSIENT_STATE:
+                case EVENT_CANCEL_HANDOVER_NO_RESPONSE:
                 case EVENT_DISPLAY_INFO_CHANGED:
                 case EVENT_WAITING_FOR_TEARING_DOWN_CONDITION_MET:
                 case EVENT_CSS_INDICATOR_CHANGED:
@@ -1416,7 +1487,10 @@
                         setupData();
                     } else {
                         mRetryDelayMillis = DataCallResponse.RETRY_DURATION_UNDEFINED;
-                        mFailCause = DataFailCause.NO_RETRY_FAILURE;
+                        if (!mFlags.keepEmptyRequestsNetwork()) {
+                            // This will mark the data profile as no retry perm failure.
+                            mFailCause = DataFailCause.NO_RETRY_FAILURE;
+                        }
                         transitionTo(mDisconnectedState);
                     }
                     break;
@@ -1494,7 +1568,7 @@
 
             int apnTypeBitmask = mDataProfile.getApnSetting() != null
                     ? mDataProfile.getApnSetting().getApnTypeBitmask() : ApnSetting.TYPE_NONE;
-            mDataCallSessionStats.onSetupDataCall(apnTypeBitmask);
+            mDataCallSessionStats.onSetupDataCall(apnTypeBitmask, isSatellite());
 
             logl("setupData: accessNetwork="
                     + AccessNetworkType.toString(accessNetwork) + ", " + mDataProfile
@@ -1542,12 +1616,7 @@
 
                 updateDataNetwork(response);
 
-                // TODO: Evaluate all network requests and see if each request still can be
-                //  satisfied.
-                //  For requests that can't be satisfied anymore, we need to put them back to the
-                //  unsatisfied pool. If none of network requests can be satisfied, then there is no
-                //  need to mark network agent connected. Just silently deactivate the data network.
-                if (mAttachedNetworkRequestList.isEmpty()) {
+                if (!mFlags.keepEmptyRequestsNetwork() && mAttachedNetworkRequestList.isEmpty()) {
                     log("Tear down the network since there is no live network request.");
                     // Directly call onTearDown here. Calling tearDown will cause deadlock because
                     // EVENT_TEAR_DOWN_NETWORK is deferred until state machine enters connected
@@ -1634,9 +1703,7 @@
 
             // If we've ever received PCO data before connected, now it's the time to process it.
             mPcoData.getOrDefault(mCid.get(mTransport), Collections.emptyMap())
-                    .forEach((pcoId, pcoData) -> {
-                        onPcoDataChanged(pcoData);
-                    });
+                    .forEach((pcoId, pcoData) -> onPcoDataChanged(pcoData));
 
             mDataNetworkCallback.invokeFromExecutor(
                     () -> mDataNetworkCallback.onLinkStatusChanged(DataNetwork.this, mLinkStatus));
@@ -1710,6 +1777,12 @@
                     // Network validation request can be accepted if the data is in connected state
                     handleDataNetworkValidationRequest((Consumer<Integer>) msg.obj);
                     break;
+                case EVENT_CANCEL_HANDOVER_NO_RESPONSE:
+                    reportAnomaly("Cancel handover no response within "
+                            + TimeUnit.MILLISECONDS.toSeconds(
+                            mDataConfigManager.getNetworkHandoverTimeoutMs())
+                            + " seconds.", "ad320988-0601-4955-836a-e6b67289c294");
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -1725,6 +1798,7 @@
     private final class HandoverState extends State {
         @Override
         public void enter() {
+            removeMessages(EVENT_CANCEL_HANDOVER_NO_RESPONSE);
             sendMessageDelayed(EVENT_STUCK_IN_TRANSIENT_STATE,
                     mDataConfigManager.getNetworkHandoverTimeoutMs());
             notifyPreciseDataConnectionState();
@@ -1908,6 +1982,9 @@
                 if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
                     unregisterForWwanEvents();
                 }
+                // Since NetworkValidation is able to request only in the Connected state,
+                // if ever connected, log for onDataNetworkDisconnected.
+                mDataNetworkValidationStats.onDataNetworkDisconnected(getDataNetworkType());
             } else {
                 mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
                         .onSetupDataFailed(DataNetwork.this,
@@ -2008,7 +2085,7 @@
                 log("Successfully attached network request " + networkRequest);
             }
         }
-        if (failedList.size() > 0) {
+        if (!failedList.isEmpty()) {
             mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
                     .onAttachFailed(DataNetwork.this, failedList));
         }
@@ -2173,8 +2250,7 @@
     private static boolean areImmutableCapabilitiesChanged(
             @NonNull NetworkCapabilities oldCapabilities,
             @NonNull NetworkCapabilities newCapabilities) {
-        if (oldCapabilities == null
-                || ArrayUtils.isEmpty(oldCapabilities.getCapabilities())) return false;
+        if (ArrayUtils.isEmpty(oldCapabilities.getCapabilities())) return false;
 
         // Remove mutable capabilities from both old and new capabilities, the remaining
         // capabilities would be immutable capabilities.
@@ -2204,6 +2280,7 @@
         // will always be registered with NOT_SUSPENDED capability.
         mNetworkAgent = createNetworkAgent();
         mNetworkAgent.markConnected();
+        notifyPreciseDataConnectionState();
         // Because network agent is always created with NOT_SUSPENDED, we need to update
         // the suspended if it's was in suspended state.
         if (mSuspended) {
@@ -2214,11 +2291,32 @@
     }
 
     /**
+     * @return {@code true} if this is a satellite data network.
+     */
+    public boolean isSatellite() {
+        return mIsSatellite;
+    }
+
+    /**
      * Update the network capabilities.
      */
     private void updateNetworkCapabilities() {
-        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
-                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
+
+        if (mFlags.satelliteInternet() && mIsSatellite
+                && mDataConfigManager.getForcedCellularTransportCapabilities().stream()
+                .noneMatch(this::hasNetworkCapabilityInNetworkRequests)) {
+            // TODO: b/328622096 remove the try/catch
+            try {
+                builder.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+            } catch (IllegalArgumentException exception) {
+                loge("TRANSPORT_SATELLITE is not supported.");
+                builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+            }
+        } else {
+            builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        }
+
         boolean roaming = mPhone.getServiceState().getDataRoaming();
 
         builder.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
@@ -2347,6 +2445,7 @@
         }
 
         // Always start with not-restricted, and then remove if needed.
+        // By default, NET_CAPABILITY_NOT_RESTRICTED and NET_CAPABILITY_NOT_CONSTRAINED are included
         builder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
 
         // When data is disabled, or data roaming is disabled and the device is roaming, we need
@@ -2401,13 +2500,13 @@
                 DataProfile dataProfile = mDataNetworkController.getDataProfileManager()
                         .getDataProfileForNetworkRequest(new TelephonyNetworkRequest(
                                 new NetworkRequest.Builder().addCapability(
-                                NetworkCapabilities.NET_CAPABILITY_MMS).build(), mPhone),
+                                NetworkCapabilities.NET_CAPABILITY_MMS).build(), mPhone, mFlags),
                         TelephonyManager.NETWORK_TYPE_IWLAN, false, false, false);
                 // If we find another data data profile that can support MMS on IWLAN, then remove
                 // the MMS capability from this cellular network. This will allow IWLAN to be
                 // brought up for MMS later.
-                if (dataProfile != null && !dataProfile.equals(mDataProfile)) {
-                    log("Found a different data profile " + mDataProfile.getApn()
+                if (dataProfile != null && !dataProfile.getApn().equals(mDataProfile.getApn())) {
+                    log("Found a different apn name " + mDataProfile.getApn()
                             + " that can serve MMS on IWLAN.");
                     builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
                 }
@@ -2426,6 +2525,23 @@
         builder.setLinkDownstreamBandwidthKbps(mNetworkBandwidth.downlinkBandwidthKbps);
         builder.setLinkUpstreamBandwidthKbps(mNetworkBandwidth.uplinkBandwidthKbps);
 
+        // Configure the network as restricted/constrained for unrestricted satellite network.
+        if (mFlags.satelliteInternet() && mIsSatellite && builder.build()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) {
+            switch (mDataConfigManager.getSatelliteDataSupportMode()) {
+                case CarrierConfigManager.SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED
+                        -> builder.removeCapability(
+                                NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+                case CarrierConfigManager.SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED -> {
+                    try {
+                        builder.removeCapability(DataUtils
+                                .NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+                    } catch (Exception ignored) { }
+                }
+                // default case CarrierConfigManager.SATELLITE_DATA_SUPPORT_ALL
+            }
+        }
+
         NetworkCapabilities nc = builder.build();
         if (mNetworkCapabilities == null || mNetworkAgent == null) {
             // This is the first time when network capabilities is created. The agent is not created
@@ -2474,21 +2590,24 @@
     /**
      * @return The network capabilities of this data network.
      */
-    public @NonNull NetworkCapabilities getNetworkCapabilities() {
+    @NonNull
+    public NetworkCapabilities getNetworkCapabilities() {
         return mNetworkCapabilities;
     }
 
     /**
      * @return The link properties of this data network.
      */
-    public @NonNull LinkProperties getLinkProperties() {
+    @NonNull
+    public LinkProperties getLinkProperties() {
         return mLinkProperties;
     }
 
     /**
      * @return The data profile of this data network.
      */
-    public @NonNull DataProfile getDataProfile() {
+    @NonNull
+    public DataProfile getDataProfile() {
         return mDataProfile;
     }
 
@@ -2509,7 +2628,6 @@
         // Never set suspended for emergency apn. Emergency data connection
         // can work while device is not in service.
         if (mNetworkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
-            newSuspendedState = false;
             // If we are not in service, change to suspended.
         } else if (nri.getRegistrationState()
                 != NetworkRegistrationInfo.REGISTRATION_STATE_HOME
@@ -2553,7 +2671,8 @@
      *
      * @return The fail cause. {@link DataFailCause#NONE} if succeeds.
      */
-    private @DataFailureCause int getFailCauseFromDataCallResponse(
+    @DataFailureCause
+    private int getFailCauseFromDataCallResponse(
             @DataServiceCallback.ResultCode int resultCode, @Nullable DataCallResponse response) {
         int failCause = DataFailCause.NONE;
         switch (resultCode) {
@@ -2584,7 +2703,8 @@
      *
      * @param response The data call response from data service.
      */
-    private void updateDataNetwork(@NonNull DataCallResponse response) {
+    private void updateDataNetwork(@Nullable DataCallResponse response) {
+        if (response == null) return;
         mCid.put(mTransport, response.getId());
         LinkProperties linkProperties = new LinkProperties();
 
@@ -2611,7 +2731,7 @@
         }
 
         // Set link addresses
-        if (response.getAddresses().size() > 0) {
+        if (!response.getAddresses().isEmpty()) {
             for (LinkAddress la : response.getAddresses()) {
                 if (!la.getAddress().isAnyLocalAddress()) {
                     logv("addr/pl=" + la.getAddress() + "/" + la.getPrefixLength());
@@ -2623,7 +2743,7 @@
         }
 
         // Set DNS servers
-        if (response.getDnsAddresses().size() > 0) {
+        if (!response.getDnsAddresses().isEmpty()) {
             for (InetAddress dns : response.getDnsAddresses()) {
                 if (!dns.isAnyLocalAddress()) {
                     linkProperties.addDnsServer(dns);
@@ -2634,7 +2754,7 @@
         }
 
         // Set PCSCF
-        if (response.getPcscfAddresses().size() > 0) {
+        if (!response.getPcscfAddresses().isEmpty()) {
             for (InetAddress pcscf : response.getPcscfAddresses()) {
                 linkProperties.addPcscfServer(pcscf);
             }
@@ -2773,22 +2893,22 @@
                             == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
                             ? "RIL" : "IWLAN data service";
                     if (protocol == ApnSetting.PROTOCOL_IP) {
-                        if (response.getAddresses().stream().anyMatch(
-                                la -> la.getAddress() instanceof java.net.Inet6Address)) {
-                            loge("Invalid DataCallResponse. Requested IPv4 but got IPv6 address."
-                                    + response);
+                        if (response.getAddresses().stream().noneMatch(
+                                la -> la.getAddress() instanceof java.net.Inet4Address)) {
+                            loge("Invalid DataCallResponse. Requested IPv4 but didn't get an "
+                                    + "IPv4 address." + response);
                             reportAnomaly(underlyingDataService + " reported mismatched IP "
-                                            + "type. Requested IPv4 but got IPv6 address.",
-                                    "7744f920-fb64-4db0-ba47-de0eae485a81");
+                                    + "type. Requested IPv4 but didn't get an IPv4 "
+                                    + "address.", "7744f920-fb64-4db0-ba47-de0eae485a82");
                         }
                     } else if (protocol == ApnSetting.PROTOCOL_IPV6) {
-                        if (response.getAddresses().stream().anyMatch(
-                                la -> la.getAddress() instanceof java.net.Inet4Address)) {
-                            loge("Invalid DataCallResponse. Requested IPv6 but got IPv4 address."
-                                    + response);
+                        if (response.getAddresses().stream().noneMatch(
+                                la -> la.getAddress() instanceof java.net.Inet6Address)) {
+                            loge("Invalid DataCallResponse. Requested IPv6 but didn't get an "
+                                    + "IPv6 address." + response);
                             reportAnomaly(underlyingDataService + " reported mismatched IP "
-                                            + "type. Requested IPv6 but got IPv4 address.",
-                                    "7744f920-fb64-4db0-ba47-de0eae485a81");
+                                    + "type. Requested IPv6 but didn't get an IPv6 "
+                                    + "address.", "7744f920-fb64-4db0-ba47-de0eae485a82");
                         }
                     }
                 }
@@ -2890,8 +3010,8 @@
      * will be performed. {@code null} if the data network is already disconnected or being
      * disconnected.
      */
-    public @Nullable Runnable tearDownWhenConditionMet(@TearDownReason int reason,
-            long timeoutMillis) {
+    @Nullable
+    public Runnable tearDownWhenConditionMet(@TearDownReason int reason, long timeoutMillis) {
         if (getCurrentState() == null || isDisconnected() || isDisconnecting()) {
             loge("tearDownWhenConditionMet: Not in the right state. State=" + getCurrentState());
             return null;
@@ -2930,6 +3050,7 @@
                 mDataCallResponse = response;
                 if (response.getLinkStatus() != DataCallResponse.LINK_STATUS_INACTIVE) {
                     updateDataNetwork(response);
+                    notifyPreciseDataConnectionState();
                 } else {
                     log("onDataStateChanged: PDN inactive reported by "
                             + AccessNetworkConstants.transportTypeToString(mTransport)
@@ -3003,12 +3124,12 @@
         NetworkBandwidth bandwidthFromConfig = mDataConfigManager.getBandwidthForNetworkType(
                 mTelephonyDisplayInfo);
 
-        if (downlinkBandwidthKbps == LinkCapacityEstimate.INVALID && bandwidthFromConfig != null) {
+        if (downlinkBandwidthKbps == LinkCapacityEstimate.INVALID) {
             // Fallback to carrier config.
             downlinkBandwidthKbps = bandwidthFromConfig.downlinkBandwidthKbps;
         }
 
-        if (uplinkBandwidthKbps == LinkCapacityEstimate.INVALID && bandwidthFromConfig != null) {
+        if (uplinkBandwidthKbps == LinkCapacityEstimate.INVALID) {
             // Fallback to carrier config.
             uplinkBandwidthKbps = bandwidthFromConfig.uplinkBandwidthKbps;
         }
@@ -3153,7 +3274,8 @@
     /**
      * @return The current network type reported by the network service.
      */
-    private @NetworkType int getDataNetworkType() {
+    @NetworkType
+    private int getDataNetworkType() {
         return getDataNetworkType(mTransport);
     }
 
@@ -3163,7 +3285,8 @@
      * @param transport The transport.
      * @return The data network type.
      */
-    private @NetworkType int getDataNetworkType(@TransportType int transport) {
+    @NetworkType
+    private int getDataNetworkType(@TransportType int transport) {
         // WLAN transport can't have network type other than IWLAN. Ideally service state tracker
         // should report the correct RAT, but sometimes race condition could happen that service
         // state is reset to out of service and RAT not updated to IWLAN yet.
@@ -3183,7 +3306,8 @@
     /**
      * @return The physical link status (i.e. RRC state).
      */
-    public @LinkStatus int getLinkStatus() {
+    @LinkStatus
+    public int getLinkStatus() {
         return mLinkStatus;
     }
 
@@ -3206,7 +3330,8 @@
     /**
      * @return Network registration info on the current transport.
      */
-    private @Nullable NetworkRegistrationInfo getNetworkRegistrationInfo() {
+    @Nullable
+    private NetworkRegistrationInfo getNetworkRegistrationInfo() {
         NetworkRegistrationInfo nri = mPhone.getServiceStateTracker().getServiceState()
                 .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS, mTransport);
         if (nri == null) {
@@ -3228,7 +3353,8 @@
      *
      * @see #getPriority()
      */
-    public @NetCapability int getApnTypeNetworkCapability() {
+    @NetCapability
+    public int getApnTypeNetworkCapability() {
         if (!mAttachedNetworkRequestList.isEmpty()) {
             // The highest priority network request is always at the top of list.
             return mAttachedNetworkRequestList.get(0).getApnTypeNetworkCapability();
@@ -3269,7 +3395,8 @@
     /**
      * @return The attached network request list.
      */
-    public @NonNull NetworkRequestList getAttachedNetworkRequestList() {
+    @NonNull
+    public NetworkRequestList getAttachedNetworkRequestList() {
         return mAttachedNetworkRequestList;
     }
 
@@ -3318,11 +3445,13 @@
     /**
      * @return The current transport of the data network.
      */
-    public @TransportType int getTransport() {
+    @TransportType
+    public int getTransport() {
         return mTransport;
     }
 
-    private @DataState int getState() {
+    @DataState
+    private int getState() {
         IState state = getCurrentState();
         if (state == null || isDisconnected()) {
             return TelephonyManager.DATA_DISCONNECTED;
@@ -3367,6 +3496,7 @@
         return new PreciseDataConnectionState.Builder()
                 .setTransportType(mTransport)
                 .setId(mCid.get(mTransport))
+                .setNetworkAgentId(mNetworkAgent.getId())
                 .setState(getState())
                 .setApnSetting(mDataProfile.getApnSetting())
                 .setLinkProperties(mLinkProperties)
@@ -3380,16 +3510,27 @@
     /**
      * Send the precise data connection state to the listener of
      * {@link android.telephony.TelephonyCallback.PreciseDataConnectionStateListener}.
+     * <p>
+     * Note that notify only when {@link DataState} or {@link
+     * PreciseDataConnectionState.NetworkValidationStatus} or {@link TelephonyNetworkAgent#getId}
+     * changes.
      */
     private void notifyPreciseDataConnectionState() {
         PreciseDataConnectionState pdcs = getPreciseDataConnectionState();
-        logv("notifyPreciseDataConnectionState=" + pdcs);
-        mPhone.notifyDataConnection(pdcs);
+        if (mPreciseDataConnectionState == null
+                || mPreciseDataConnectionState.getState() != pdcs.getState()
+                || mPreciseDataConnectionState.getNetworkValidationStatus()
+                        != pdcs.getNetworkValidationStatus()
+                || mPreciseDataConnectionState.getNetId() != pdcs.getNetId()) {
+            mPreciseDataConnectionState = pdcs;
+            logv("notifyPreciseDataConnectionState=" + pdcs);
+            mPhone.notifyDataConnection(pdcs);
+        }
     }
 
     /**
      * Request the data network to handover to the target transport.
-     *
+     * <p>
      * This is the starting point of initiating IWLAN/cellular handover. It will first call
      * {@link DataServiceManager#startHandover(int, Message)} to notify source transport that
      * handover is about to start, and then call {@link DataServiceManager#setupDataCall(int,
@@ -3474,6 +3615,8 @@
                 DataService.REQUEST_REASON_HANDOVER, mLinkProperties, mPduSessionId,
                 mNetworkSliceInfo, mHandoverDataProfile.getTrafficDescriptor(), true,
                 obtainMessage(EVENT_HANDOVER_RESPONSE, retryEntry));
+
+        mDataNetworkValidationStats.onHandoverAttempted();
     }
 
     /**
@@ -3521,6 +3664,8 @@
             // id can be released if it is preserved for handover.
             mDataServiceManagers.get(mTransport).cancelHandover(mCid.get(mTransport),
                     obtainMessage(EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE));
+            sendMessageDelayed(EVENT_CANCEL_HANDOVER_NO_RESPONSE,
+                    mDataConfigManager.getNetworkHandoverTimeoutMs());
 
             long retry = response != null ? response.getRetryDurationMillis()
                     : DataCallResponse.RETRY_DURATION_UNDEFINED;
@@ -3596,7 +3741,8 @@
     /**
      * @return The last known data network type of the data network.
      */
-    public @NetworkType int getLastKnownDataNetworkType() {
+    @NetworkType
+    public int getLastKnownDataNetworkType() {
         return mLastKnownDataNetworkType;
     }
 
@@ -3610,7 +3756,8 @@
     /**
      * @return The PCO data received from the network.
      */
-    public @NonNull Map<Integer, PcoData> getPcoData() {
+    @NonNull
+    public Map<Integer, PcoData> getPcoData() {
         if (mTransport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN
                 || mCid.get(mTransport) == INVALID_CID) {
             return Collections.emptyMap();
@@ -3644,7 +3791,7 @@
     }
 
     /**
-     * The network validation requests moves to process on the statemachich handler. A request is
+     * The network validation requests moves to process on the state machine handler. A request is
      * processed according to state of the data network.
      */
     public void requestNetworkValidation(@NonNull Consumer<Integer> resultCodeCallback) {
@@ -3668,6 +3815,11 @@
         // Request validation directly from the data service.
         mDataServiceManagers.get(mTransport).requestNetworkValidation(
                 mCid.get(mTransport), obtainMessage(EVENT_DATA_NETWORK_VALIDATION_RESPONSE));
+
+        int apnTypeBitmask = mDataProfile.getApnSetting() != null
+                ? mDataProfile.getApnSetting().getApnTypeBitmask() : ApnSetting.TYPE_NONE;
+        mDataNetworkValidationStats.onRequestNetworkValidation(apnTypeBitmask);
+
         log("handleDataNetworkValidationRequest, network validation requested");
     }
 
@@ -3696,8 +3848,7 @@
 
     /**
      * Update the validation status from {@link DataCallResponse}, convert to network validation
-     * status {@link PreciseDataConnectionState.NetworkValidationStatus} and notify to
-     * {@link PreciseDataConnectionState} if status was changed.
+     * status {@link PreciseDataConnectionState.NetworkValidationStatus}.
      *
      * @param networkValidationStatus {@link PreciseDataConnectionState.NetworkValidationStatus}
      */
@@ -3714,8 +3865,10 @@
                     + PreciseDataConnectionState.networkValidationStatusToString(
                     networkValidationStatus));
             mNetworkValidationStatus = networkValidationStatus;
-            notifyPreciseDataConnectionState();
         }
+
+        mDataNetworkValidationStats.onUpdateNetworkValidationState(
+                mNetworkValidationStatus, getDataNetworkType());
     }
 
     /**
@@ -3724,75 +3877,54 @@
      * @param reason Data deactivation reason.
      * @return The deactivation reason in string format.
      */
-    public static @NonNull String tearDownReasonToString(@TearDownReason int reason) {
-        switch (reason) {
-            case TEAR_DOWN_REASON_NONE:
-                return "NONE";
-            case TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED:
-                return "CONNECTIVITY_SERVICE_UNWANTED";
-            case TEAR_DOWN_REASON_SIM_REMOVAL:
-                return "SIM_REMOVAL";
-            case TEAR_DOWN_REASON_AIRPLANE_MODE_ON:
-                return "AIRPLANE_MODE_ON";
-            case TEAR_DOWN_REASON_DATA_DISABLED:
-                return "DATA_DISABLED";
-            case TEAR_DOWN_REASON_NO_LIVE_REQUEST:
-                return "TEAR_DOWN_REASON_NO_LIVE_REQUEST";
-            case TEAR_DOWN_REASON_RAT_NOT_ALLOWED:
-                return "TEAR_DOWN_REASON_RAT_NOT_ALLOWED";
-            case TEAR_DOWN_REASON_ROAMING_DISABLED:
-                return "TEAR_DOWN_REASON_ROAMING_DISABLED";
-            case TEAR_DOWN_REASON_CONCURRENT_VOICE_DATA_NOT_ALLOWED:
-                return "TEAR_DOWN_REASON_CONCURRENT_VOICE_DATA_NOT_ALLOWED";
-            case TEAR_DOWN_REASON_SERVICE_OPTION_NOT_SUPPORTED:
-                return "TEAR_DOWN_REASON_SERVICE_OPTION_NOT_SUPPORTED";
-            case TEAR_DOWN_REASON_DATA_SERVICE_NOT_READY:
-                return "TEAR_DOWN_REASON_DATA_SERVICE_NOT_READY";
-            case TEAR_DOWN_REASON_POWER_OFF_BY_CARRIER:
-                return "TEAR_DOWN_REASON_POWER_OFF_BY_CARRIER";
-            case TEAR_DOWN_REASON_DATA_STALL:
-                return "TEAR_DOWN_REASON_DATA_STALL";
-            case TEAR_DOWN_REASON_HANDOVER_FAILED:
-                return "TEAR_DOWN_REASON_HANDOVER_FAILED";
-            case TEAR_DOWN_REASON_HANDOVER_NOT_ALLOWED:
-                return "TEAR_DOWN_REASON_HANDOVER_NOT_ALLOWED";
-            case TEAR_DOWN_REASON_VCN_REQUESTED:
-                return "TEAR_DOWN_REASON_VCN_REQUESTED";
-            case TEAR_DOWN_REASON_VOPS_NOT_SUPPORTED:
-                return "TEAR_DOWN_REASON_VOPS_NOT_SUPPORTED";
-            case TEAR_DOWN_REASON_DEFAULT_DATA_UNSELECTED:
-                return "TEAR_DOWN_REASON_DEFAULT_DATA_UNSELECTED";
-            case TEAR_DOWN_REASON_NOT_IN_SERVICE:
-                return "TEAR_DOWN_REASON_NOT_IN_SERVICE";
-            case TEAR_DOWN_REASON_DATA_CONFIG_NOT_READY:
-                return "TEAR_DOWN_REASON_DATA_CONFIG_NOT_READY";
-            case TEAR_DOWN_REASON_PENDING_TEAR_DOWN_ALL:
-                return "TEAR_DOWN_REASON_PENDING_TEAR_DOWN_ALL";
-            case TEAR_DOWN_REASON_NO_SUITABLE_DATA_PROFILE:
-                return "TEAR_DOWN_REASON_NO_SUITABLE_DATA_PROFILE";
-            case TEAR_DOWN_REASON_CDMA_EMERGENCY_CALLBACK_MODE:
-                return "TEAR_DOWN_REASON_CDMA_EMERGENCY_CALLBACK_MODE";
-            case TEAR_DOWN_REASON_RETRY_SCHEDULED:
-                return "TEAR_DOWN_REASON_RETRY_SCHEDULED";
-            case TEAR_DOWN_REASON_DATA_THROTTLED:
-                return "TEAR_DOWN_REASON_DATA_THROTTLED";
-            case TEAR_DOWN_REASON_DATA_PROFILE_INVALID:
-                return "TEAR_DOWN_REASON_DATA_PROFILE_INVALID";
-            case TEAR_DOWN_REASON_DATA_PROFILE_NOT_PREFERRED:
-                return "TEAR_DOWN_REASON_DATA_PROFILE_NOT_PREFERRED";
-            case TEAR_DOWN_REASON_NOT_ALLOWED_BY_POLICY:
-                return "TEAR_DOWN_REASON_NOT_ALLOWED_BY_POLICY";
-            case TEAR_DOWN_REASON_ILLEGAL_STATE:
-                return "TEAR_DOWN_REASON_ILLEGAL_STATE";
-            case TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK:
-                return "TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK";
-            case TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED:
-                return "TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED";
-            case TEAR_DOWN_REASON_DATA_LIMIT_REACHED:
-                return "TEAR_DOWN_REASON_DATA_LIMIT_REACHED";
-            default:
-                return "UNKNOWN(" + reason + ")";
-        }
+    @NonNull
+    public static String tearDownReasonToString(@TearDownReason int reason) {
+        return switch (reason) {
+            case TEAR_DOWN_REASON_NONE -> "NONE";
+            case TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED -> "CONNECTIVITY_SERVICE_UNWANTED";
+            case TEAR_DOWN_REASON_SIM_REMOVAL -> "SIM_REMOVAL";
+            case TEAR_DOWN_REASON_AIRPLANE_MODE_ON -> "AIRPLANE_MODE_ON";
+            case TEAR_DOWN_REASON_DATA_DISABLED -> "DATA_DISABLED";
+            case TEAR_DOWN_REASON_NO_LIVE_REQUEST -> "TEAR_DOWN_REASON_NO_LIVE_REQUEST";
+            case TEAR_DOWN_REASON_RAT_NOT_ALLOWED -> "TEAR_DOWN_REASON_RAT_NOT_ALLOWED";
+            case TEAR_DOWN_REASON_ROAMING_DISABLED -> "TEAR_DOWN_REASON_ROAMING_DISABLED";
+            case TEAR_DOWN_REASON_CONCURRENT_VOICE_DATA_NOT_ALLOWED ->
+                    "TEAR_DOWN_REASON_CONCURRENT_VOICE_DATA_NOT_ALLOWED";
+            case TEAR_DOWN_REASON_SERVICE_OPTION_NOT_SUPPORTED ->
+                    "TEAR_DOWN_REASON_SERVICE_OPTION_NOT_SUPPORTED";
+            case TEAR_DOWN_REASON_DATA_SERVICE_NOT_READY ->
+                    "TEAR_DOWN_REASON_DATA_SERVICE_NOT_READY";
+            case TEAR_DOWN_REASON_POWER_OFF_BY_CARRIER -> "TEAR_DOWN_REASON_POWER_OFF_BY_CARRIER";
+            case TEAR_DOWN_REASON_DATA_STALL -> "TEAR_DOWN_REASON_DATA_STALL";
+            case TEAR_DOWN_REASON_HANDOVER_FAILED -> "TEAR_DOWN_REASON_HANDOVER_FAILED";
+            case TEAR_DOWN_REASON_HANDOVER_NOT_ALLOWED -> "TEAR_DOWN_REASON_HANDOVER_NOT_ALLOWED";
+            case TEAR_DOWN_REASON_VCN_REQUESTED -> "TEAR_DOWN_REASON_VCN_REQUESTED";
+            case TEAR_DOWN_REASON_VOPS_NOT_SUPPORTED -> "TEAR_DOWN_REASON_VOPS_NOT_SUPPORTED";
+            case TEAR_DOWN_REASON_DEFAULT_DATA_UNSELECTED ->
+                    "TEAR_DOWN_REASON_DEFAULT_DATA_UNSELECTED";
+            case TEAR_DOWN_REASON_NOT_IN_SERVICE -> "TEAR_DOWN_REASON_NOT_IN_SERVICE";
+            case TEAR_DOWN_REASON_DATA_CONFIG_NOT_READY -> "TEAR_DOWN_REASON_DATA_CONFIG_NOT_READY";
+            case TEAR_DOWN_REASON_PENDING_TEAR_DOWN_ALL -> "TEAR_DOWN_REASON_PENDING_TEAR_DOWN_ALL";
+            case TEAR_DOWN_REASON_NO_SUITABLE_DATA_PROFILE ->
+                    "TEAR_DOWN_REASON_NO_SUITABLE_DATA_PROFILE";
+            case TEAR_DOWN_REASON_CDMA_EMERGENCY_CALLBACK_MODE ->
+                    "TEAR_DOWN_REASON_CDMA_EMERGENCY_CALLBACK_MODE";
+            case TEAR_DOWN_REASON_RETRY_SCHEDULED -> "TEAR_DOWN_REASON_RETRY_SCHEDULED";
+            case TEAR_DOWN_REASON_DATA_THROTTLED -> "TEAR_DOWN_REASON_DATA_THROTTLED";
+            case TEAR_DOWN_REASON_DATA_PROFILE_INVALID -> "TEAR_DOWN_REASON_DATA_PROFILE_INVALID";
+            case TEAR_DOWN_REASON_DATA_PROFILE_NOT_PREFERRED ->
+                    "TEAR_DOWN_REASON_DATA_PROFILE_NOT_PREFERRED";
+            case TEAR_DOWN_REASON_NOT_ALLOWED_BY_POLICY -> "TEAR_DOWN_REASON_NOT_ALLOWED_BY_POLICY";
+            case TEAR_DOWN_REASON_ILLEGAL_STATE -> "TEAR_DOWN_REASON_ILLEGAL_STATE";
+            case TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK ->
+                    "TEAR_DOWN_REASON_ONLY_ALLOWED_SINGLE_NETWORK";
+            case TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED ->
+                    "TEAR_DOWN_REASON_PREFERRED_DATA_SWITCHED";
+            case TEAR_DOWN_REASON_DATA_LIMIT_REACHED -> "TEAR_DOWN_REASON_DATA_LIMIT_REACHED";
+            case TEAR_DOWN_REASON_DATA_NETWORK_TRANSPORT_NOT_ALLOWED ->
+                    "TEAR_DOWN_REASON_DATA_NETWORK_TRANSPORT_NOT_ALLOWED";
+            default -> "UNKNOWN(" + reason + ")";
+        };
     }
 
     /**
@@ -3801,65 +3933,43 @@
      * @param event The event
      * @return The event in string format.
      */
-    private static @NonNull String eventToString(int event) {
-        switch (event) {
-            case EVENT_DATA_CONFIG_UPDATED:
-                return "EVENT_DATA_CONFIG_UPDATED";
-            case EVENT_ATTACH_NETWORK_REQUEST:
-                return "EVENT_ATTACH_NETWORK_REQUEST";
-            case EVENT_DETACH_NETWORK_REQUEST:
-                return "EVENT_DETACH_NETWORK_REQUEST";
-            case EVENT_RADIO_NOT_AVAILABLE:
-                return "EVENT_RADIO_NOT_AVAILABLE";
-            case EVENT_ALLOCATE_PDU_SESSION_ID_RESPONSE:
-                return "EVENT_ALLOCATE_PDU_SESSION_ID_RESPONSE";
-            case EVENT_SETUP_DATA_NETWORK_RESPONSE:
-                return "EVENT_SETUP_DATA_NETWORK_RESPONSE";
-            case EVENT_TEAR_DOWN_NETWORK:
-                return "EVENT_TEAR_DOWN_NETWORK";
-            case EVENT_DATA_STATE_CHANGED:
-                return "EVENT_DATA_STATE_CHANGED";
-            case EVENT_SERVICE_STATE_CHANGED:
-                return "EVENT_DATA_NETWORK_TYPE_REG_STATE_CHANGED";
-            case EVENT_DETACH_ALL_NETWORK_REQUESTS:
-                return "EVENT_DETACH_ALL_NETWORK_REQUESTS";
-            case EVENT_BANDWIDTH_ESTIMATE_FROM_MODEM_CHANGED:
-                return "EVENT_BANDWIDTH_ESTIMATE_FROM_MODEM_CHANGED";
-            case EVENT_DISPLAY_INFO_CHANGED:
-                return "EVENT_DISPLAY_INFO_CHANGED";
-            case EVENT_HANDOVER_RESPONSE:
-                return "EVENT_HANDOVER_RESPONSE";
-            case EVENT_SUBSCRIPTION_PLAN_OVERRIDE:
-                return "EVENT_SUBSCRIPTION_PLAN_OVERRIDE";
-            case EVENT_PCO_DATA_RECEIVED:
-                return "EVENT_PCO_DATA_RECEIVED";
-            case EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED:
-                return "EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED";
-            case EVENT_DEACTIVATE_DATA_NETWORK_RESPONSE:
-                return "EVENT_DEACTIVATE_DATA_NETWORK_RESPONSE";
-            case EVENT_STUCK_IN_TRANSIENT_STATE:
-                return "EVENT_STUCK_IN_TRANSIENT_STATE";
-            case EVENT_WAITING_FOR_TEARING_DOWN_CONDITION_MET:
-                return "EVENT_WAITING_FOR_TEARING_DOWN_CONDITION_MET";
-            case EVENT_VOICE_CALL_STARTED:
-                return "EVENT_VOICE_CALL_STARTED";
-            case EVENT_VOICE_CALL_ENDED:
-                return "EVENT_VOICE_CALL_ENDED";
-            case EVENT_CSS_INDICATOR_CHANGED:
-                return "EVENT_CSS_INDICATOR_CHANGED";
-            case EVENT_NOTIFY_HANDOVER_STARTED:
-                return "EVENT_NOTIFY_HANDOVER_STARTED";
-            case EVENT_NOTIFY_HANDOVER_STARTED_RESPONSE:
-                return "EVENT_NOTIFY_HANDOVER_STARTED_RESPONSE";
-            case EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE:
-                return "EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE";
-            case EVENT_DATA_NETWORK_VALIDATION_REQUESTED:
-                return "EVENT_DATA_NETWORK_VALIDATION_REQUESTED";
-            case EVENT_DATA_NETWORK_VALIDATION_RESPONSE:
-                return "EVENT_DATA_NETWORK_VALIDATION_RESPONSE";
-            default:
-                return "Unknown(" + event + ")";
-        }
+    @NonNull
+    private static String eventToString(int event) {
+        return switch (event) {
+            case EVENT_DATA_CONFIG_UPDATED -> "EVENT_DATA_CONFIG_UPDATED";
+            case EVENT_ATTACH_NETWORK_REQUEST -> "EVENT_ATTACH_NETWORK_REQUEST";
+            case EVENT_DETACH_NETWORK_REQUEST -> "EVENT_DETACH_NETWORK_REQUEST";
+            case EVENT_RADIO_NOT_AVAILABLE -> "EVENT_RADIO_NOT_AVAILABLE";
+            case EVENT_ALLOCATE_PDU_SESSION_ID_RESPONSE -> "EVENT_ALLOCATE_PDU_SESSION_ID_RESPONSE";
+            case EVENT_SETUP_DATA_NETWORK_RESPONSE -> "EVENT_SETUP_DATA_NETWORK_RESPONSE";
+            case EVENT_TEAR_DOWN_NETWORK -> "EVENT_TEAR_DOWN_NETWORK";
+            case EVENT_DATA_STATE_CHANGED -> "EVENT_DATA_STATE_CHANGED";
+            case EVENT_SERVICE_STATE_CHANGED -> "EVENT_DATA_NETWORK_TYPE_REG_STATE_CHANGED";
+            case EVENT_DETACH_ALL_NETWORK_REQUESTS -> "EVENT_DETACH_ALL_NETWORK_REQUESTS";
+            case EVENT_BANDWIDTH_ESTIMATE_FROM_MODEM_CHANGED ->
+                    "EVENT_BANDWIDTH_ESTIMATE_FROM_MODEM_CHANGED";
+            case EVENT_CANCEL_HANDOVER_NO_RESPONSE -> "EVENT_CANCEL_HANDOVER_NO_RESPONSE";
+            case EVENT_DISPLAY_INFO_CHANGED -> "EVENT_DISPLAY_INFO_CHANGED";
+            case EVENT_HANDOVER_RESPONSE -> "EVENT_HANDOVER_RESPONSE";
+            case EVENT_SUBSCRIPTION_PLAN_OVERRIDE -> "EVENT_SUBSCRIPTION_PLAN_OVERRIDE";
+            case EVENT_PCO_DATA_RECEIVED -> "EVENT_PCO_DATA_RECEIVED";
+            case EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED -> "EVENT_CARRIER_PRIVILEGED_UIDS_CHANGED";
+            case EVENT_DEACTIVATE_DATA_NETWORK_RESPONSE -> "EVENT_DEACTIVATE_DATA_NETWORK_RESPONSE";
+            case EVENT_STUCK_IN_TRANSIENT_STATE -> "EVENT_STUCK_IN_TRANSIENT_STATE";
+            case EVENT_WAITING_FOR_TEARING_DOWN_CONDITION_MET ->
+                    "EVENT_WAITING_FOR_TEARING_DOWN_CONDITION_MET";
+            case EVENT_VOICE_CALL_STARTED -> "EVENT_VOICE_CALL_STARTED";
+            case EVENT_VOICE_CALL_ENDED -> "EVENT_VOICE_CALL_ENDED";
+            case EVENT_CSS_INDICATOR_CHANGED -> "EVENT_CSS_INDICATOR_CHANGED";
+            case EVENT_NOTIFY_HANDOVER_STARTED -> "EVENT_NOTIFY_HANDOVER_STARTED";
+            case EVENT_NOTIFY_HANDOVER_STARTED_RESPONSE -> "EVENT_NOTIFY_HANDOVER_STARTED_RESPONSE";
+            case EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE ->
+                    "EVENT_NOTIFY_HANDOVER_CANCELLED_RESPONSE";
+            case EVENT_DATA_NETWORK_VALIDATION_REQUESTED ->
+                    "EVENT_DATA_NETWORK_VALIDATION_REQUESTED";
+            case EVENT_DATA_NETWORK_VALIDATION_RESPONSE -> "EVENT_DATA_NETWORK_VALIDATION_RESPONSE";
+            default -> "Unknown(" + event + ")";
+        };
     }
 
     @Override
@@ -3872,7 +3982,8 @@
     /**
      * @return The short name of the data network (e.g. DN-C-1)
      */
-    public @NonNull String name() {
+    @NonNull
+    public String name() {
         return mLogTag;
     }
 
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 70d3b23..30172db 100644
--- a/src/java/com/android/internal/telephony/data/DataNetworkController.java
+++ b/src/java/com/android/internal/telephony/data/DataNetworkController.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.data;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.ElapsedRealtimeLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -38,6 +39,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.SystemClock;
 import android.telecom.TelecomManager;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
@@ -90,6 +92,7 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.SlidingWindowEventCounter;
+import com.android.internal.telephony.TelephonyCapabilities;
 import com.android.internal.telephony.TelephonyComponentFactory;
 import com.android.internal.telephony.data.AccessNetworksManager.AccessNetworksManagerCallback;
 import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
@@ -244,32 +247,46 @@
             TimeUnit.SECONDS.toMillis(1);
 
     /**
-     * The delay in milliseconds to re-evaluate existing data networks for bootstrap sim data usage
-     * limit.
+     * The guard timer in milliseconds to limit querying the data usage api stats frequently
      */
-    private static final long REEVALUATE_BOOTSTRAP_SIM_DATA_USAGE_MILLIS =
-            TimeUnit.SECONDS.toMillis(60);
+    private static final long GUARD_TIMER_INTERVAL_TO_QUERY_DATA_USAGE_API_STATS_MILLIS =
+            TimeUnit.SECONDS.toMillis(1);
 
     /**
      * bootstrap sim total data usage bytes
      */
     private long mBootStrapSimTotalDataUsageBytes = 0L;
 
+    /**
+     * bootstrap sim last data usage query time
+     */
+    @ElapsedRealtimeLong
+    private long mBootstrapSimLastDataUsageQueryTime = 0L;
+
     private final Phone mPhone;
     private final String mLogTag;
     private final LocalLog mLocalLog = new LocalLog(128);
 
-    private final @NonNull DataConfigManager mDataConfigManager;
-    private final @NonNull DataSettingsManager mDataSettingsManager;
-    private final @NonNull DataProfileManager mDataProfileManager;
-    private final @NonNull DataStallRecoveryManager mDataStallRecoveryManager;
-    private final @NonNull AccessNetworksManager mAccessNetworksManager;
-    private final @NonNull DataRetryManager mDataRetryManager;
-    private final @NonNull ImsManager mImsManager;
-    private final @NonNull TelecomManager mTelecomManager;
-    private final @NonNull NetworkPolicyManager mNetworkPolicyManager;
-    private final @NonNull SparseArray<DataServiceManager> mDataServiceManagers =
-            new SparseArray<>();
+    @NonNull
+    private final DataConfigManager mDataConfigManager;
+    @NonNull
+    private final DataSettingsManager mDataSettingsManager;
+    @NonNull
+    private final DataProfileManager mDataProfileManager;
+    @NonNull
+    private final DataStallRecoveryManager mDataStallRecoveryManager;
+    @NonNull
+    private final AccessNetworksManager mAccessNetworksManager;
+    @NonNull
+    private final DataRetryManager mDataRetryManager;
+    @NonNull
+    private final ImsManager mImsManager;
+    @NonNull
+    private final TelecomManager mTelecomManager;
+    @NonNull
+    private final NetworkPolicyManager mNetworkPolicyManager;
+    @NonNull
+    private final SparseArray<DataServiceManager> mDataServiceManagers = new SparseArray<>();
 
     /** The subscription index associated with this data network controller. */
     private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -277,35 +294,41 @@
     /** The current service state of the device. */
     // Note that keeping a copy here instead of directly using ServiceStateTracker.getServiceState()
     // is intended for detecting the delta.
-    private @NonNull ServiceState mServiceState;
+    @NonNull
+    private ServiceState mServiceState;
 
     /** The list of SubscriptionPlans, updated when initialized and when plans are changed. */
-    private final @NonNull List<SubscriptionPlan> mSubscriptionPlans = new ArrayList<>();
+    @NonNull
+    private final List<SubscriptionPlan> mSubscriptionPlans = new ArrayList<>();
 
     /**
      * The set of network types an unmetered override applies to, set by onSubscriptionOverride
      * and cleared when the device is rebooted or the override expires.
      */
-    private final @NonNull @NetworkType Set<Integer> mUnmeteredOverrideNetworkTypes =
-            new ArraySet<>();
+    @NonNull
+    @NetworkType
+    private final Set<Integer> mUnmeteredOverrideNetworkTypes = new ArraySet<>();
 
     /**
      * The set of network types a congested override applies to, set by onSubscriptionOverride
      * and cleared when the device is rebooted or the override expires.
      */
-    private final @NonNull @NetworkType Set<Integer> mCongestedOverrideNetworkTypes =
-            new ArraySet<>();
+    @NonNull
+    @NetworkType
+    private final Set<Integer> mCongestedOverrideNetworkTypes = new ArraySet<>();
 
     /**
      * The list of all network requests.
      */
-    private final @NonNull NetworkRequestList mAllNetworkRequestList = new NetworkRequestList();
+    @NonNull
+    private final NetworkRequestList mAllNetworkRequestList = new NetworkRequestList();
 
     /**
      * The current data network list, including the ones that are connected, connecting, or
      * disconnecting.
      */
-    private final @NonNull List<DataNetwork> mDataNetworkList = new ArrayList<>();
+    @NonNull
+    private final List<DataNetwork> mDataNetworkList = new ArrayList<>();
 
     /** {@code true} indicating at least one data network exists. */
     private boolean mAnyDataNetworkExisting;
@@ -313,27 +336,33 @@
     /**
      * Contain the last 10 data networks that were connected. This is for debugging purposes only.
      */
-    private final @NonNull List<DataNetwork> mPreviousConnectedDataNetworkList = new ArrayList<>();
+    @NonNull
+    private final List<DataNetwork> mPreviousConnectedDataNetworkList = new ArrayList<>();
 
     /**
      * The internet data network state. Note that this is the best effort if more than one
      * data network supports internet.
      */
-    private @DataState int mInternetDataNetworkState = TelephonyManager.DATA_DISCONNECTED;
+    @DataState
+    private int mInternetDataNetworkState = TelephonyManager.DATA_DISCONNECTED;
 
     /** All the current connected/handover internet networks.  */
-    @NonNull private Set<DataNetwork> mConnectedInternetNetworks = new HashSet<>();
+    @NonNull
+    private Set<DataNetwork> mConnectedInternetNetworks = new HashSet<>();
 
     /**
      * The IMS data network state. For now this is just for debugging purposes.
      */
-    private @DataState int mImsDataNetworkState = TelephonyManager.DATA_DISCONNECTED;
+    @DataState
+    private int mImsDataNetworkState = TelephonyManager.DATA_DISCONNECTED;
 
     /** Overall aggregated link status from internet data networks. */
-    private @LinkStatus int mInternetLinkStatus = DataCallResponse.LINK_STATUS_UNKNOWN;
+    @LinkStatus
+    private int mInternetLinkStatus = DataCallResponse.LINK_STATUS_UNKNOWN;
 
     /** Data network controller callbacks. */
-    private final @NonNull Set<DataNetworkControllerCallback> mDataNetworkControllerCallbacks =
+    @NonNull
+    private final Set<DataNetworkControllerCallback> mDataNetworkControllerCallbacks =
             new ArraySet<>();
 
     /** Indicates if packet switch data is restricted by the cellular network. */
@@ -349,54 +378,59 @@
      * Indicates if the data services are bound. Key if the transport type, and value is the boolean
      * indicating service is bound or not.
      */
-    private final @NonNull SparseBooleanArray mDataServiceBound = new SparseBooleanArray();
+    @NonNull
+    private final SparseBooleanArray mDataServiceBound = new SparseBooleanArray();
 
     /** SIM state. */
-    private @SimState int mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
+    @SimState
+    private int mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
 
     /** Data activity. */
-    private @DataActivityType int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
+    @DataActivityType
+    private int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
 
     /**
      * IMS state callbacks. Key is the IMS feature, value is the callback.
      */
-    private final @NonNull SparseArray<ImsStateCallback> mImsStateCallbacks = new SparseArray<>();
+    @NonNull
+    private final SparseArray<ImsStateCallback> mImsStateCallbacks = new SparseArray<>();
 
     /** Registered IMS features. Unregistered IMS features are removed from the set. */
-    private final @NonNull Set<Integer> mRegisteredImsFeatures = new ArraySet<>();
+    @NonNull
+    private final Set<Integer> mRegisteredImsFeatures = new ArraySet<>();
 
     /** IMS feature package names. Key is the IMS feature, value is the package name. */
-    private final @NonNull SparseArray<String> mImsFeaturePackageName = new SparseArray<>();
+    @NonNull
+    private final SparseArray<String> mImsFeaturePackageName = new SparseArray<>();
 
     /**
      * Networks that are pending IMS de-registration. Key is the data network, value is the function
      * to tear down the network.
      */
-    private final @NonNull Map<DataNetwork, Runnable> mPendingImsDeregDataNetworks =
-            new ArrayMap<>();
+    @NonNull
+    private final Map<DataNetwork, Runnable> mPendingImsDeregDataNetworks = new ArrayMap<>();
 
     /**
      * IMS feature registration callback. The key is the IMS feature, the value is the registration
      * callback. When new SIM inserted, the old callbacks associated with the old subscription index
      * will be unregistered.
      */
-    private final @NonNull SparseArray<RegistrationManager.RegistrationCallback>
+    @NonNull
+    private final SparseArray<RegistrationManager.RegistrationCallback>
             mImsFeatureRegistrationCallbacks = new SparseArray<>();
 
     /** The counter to detect back to back release/request IMS network. */
-    private @NonNull SlidingWindowEventCounter mImsThrottleCounter;
+    @NonNull
+    private SlidingWindowEventCounter mImsThrottleCounter;
     /** Event counter for unwanted network within time window, is used to trigger anomaly report. */
-    private @NonNull SlidingWindowEventCounter mNetworkUnwantedCounter;
+    @NonNull
+    private SlidingWindowEventCounter mNetworkUnwantedCounter;
     /** Event counter for WLAN setup data failure within time window to trigger anomaly report. */
-    private @NonNull SlidingWindowEventCounter mSetupDataCallWlanFailureCounter;
+    @NonNull
+    private SlidingWindowEventCounter mSetupDataCallWlanFailureCounter;
     /** Event counter for WWAN setup data failure within time window to trigger anomaly report. */
-    private @NonNull SlidingWindowEventCounter mSetupDataCallWwanFailureCounter;
-
-    /**
-     * {@code true} if {@link #tearDownAllDataNetworks(int)} was invoked and waiting for all
-     * networks torn down.
-     */
-    private boolean mPendingTearDownAllNetworks = false;
+    @NonNull
+    private SlidingWindowEventCounter mSetupDataCallWwanFailureCounter;
 
     /**
      * The capabilities of the latest released IMS request. To detect back to back release/request
@@ -407,7 +441,8 @@
     /** True after try to release an IMS network; False after try to request an IMS network. */
     private boolean mLastImsOperationIsRelease;
 
-    private final @NonNull FeatureFlags mFeatureFlags;
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
 
     /** The broadcast receiver. */
     private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@@ -428,7 +463,7 @@
     };
 
     private boolean hasCalling() {
-        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        if (!TelephonyCapabilities.minimalTelephonyCdmCheck(mFeatureFlags)) return true;
         return mPhone.getContext().getPackageManager().hasSystemFeature(
             PackageManager.FEATURE_TELEPHONY_CALLING);
     }
@@ -436,7 +471,7 @@
     /**
      * The sorted network request list by priority. The highest priority network request stays at
      * the head of the list. The highest priority is 100, the lowest is 0.
-     *
+     * <p>
      * Note this list is not thread-safe. Do not access the list from different threads.
      */
     @VisibleForTesting
@@ -519,7 +554,8 @@
          * @return The first network request in the list that contains all the provided
          * capabilities.
          */
-        public @Nullable TelephonyNetworkRequest get(@NonNull @NetCapability int[] netCaps) {
+        @Nullable
+        public TelephonyNetworkRequest get(@NonNull @NetCapability int[] netCaps) {
             int index = 0;
             while (index < size()) {
                 TelephonyNetworkRequest networkRequest = get(index);
@@ -528,7 +564,7 @@
                         .boxed()
                         .collect(Collectors.toSet())
                         .containsAll(Arrays.stream(netCaps).boxed()
-                                .collect(Collectors.toList()))) {
+                                .toList())) {
                     return networkRequest;
                 }
                 index++;
@@ -559,6 +595,16 @@
         }
 
         /**
+         * Print "capabilities - connectivity transport". e.g. INTERNET|NOT_RESTRICTED-SATELLITE
+         */
+        @NonNull
+        public String toStringSimplified() {
+            return size() > 0 ? DataUtils.networkCapabilitiesToString(get(0).getCapabilities())
+                    + "-" + DataUtils.connectivityTransportsToString(get(0).getTransportTypes())
+                    : "";
+        }
+
+        /**
          * Dump the network request list.
          *
          * @param pw print writer.
@@ -696,19 +742,26 @@
         private static final String RULE_TAG_ROAMING = "roaming";
 
         /** Handover rule type. */
-        public final @HandoverRuleType int type;
+        @HandoverRuleType
+        public final int type;
 
         /** The applicable source access networks for handover. */
-        public final @NonNull @RadioAccessNetworkType Set<Integer> sourceAccessNetworks;
+        @NonNull
+        @RadioAccessNetworkType
+        public final Set<Integer> sourceAccessNetworks;
 
         /** The applicable target access networks for handover. */
-        public final @NonNull @RadioAccessNetworkType Set<Integer> targetAccessNetworks;
+        @NonNull
+        @RadioAccessNetworkType
+        public final Set<Integer> targetAccessNetworks;
 
         /**
          * The network capabilities to any of which this handover rule applies.
          * If is empty, then capability is ignored as a rule matcher.
          */
-        public final @NonNull @NetCapability Set<Integer> networkCapabilities;
+        @NonNull
+        @NetCapability
+        public final Set<Integer> networkCapabilities;
 
         /** {@code true} indicates this policy is only applicable when the device is roaming. */
         public final boolean isOnlyForRoaming;
@@ -1185,7 +1238,7 @@
                 mSubscriptionPlans.clear();
                 mSubscriptionPlans.addAll(Arrays.asList(plans));
                 mDataNetworkControllerCallbacks.forEach(cb -> cb.invokeFromExecutor(
-                        () -> cb.onSubscriptionPlanOverride()));
+                        cb::onSubscriptionPlanOverride));
                 break;
             case EVENT_SUBSCRIPTION_OVERRIDE:
                 int overrideMask = msg.arg1;
@@ -1205,7 +1258,7 @@
                         }
                     }
                     mDataNetworkControllerCallbacks.forEach(cb -> cb.invokeFromExecutor(
-                            () -> cb.onSubscriptionPlanOverride()));
+                            cb::onSubscriptionPlanOverride));
                 } else if (overrideMask == NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_CONGESTED) {
                     log("Congested subscription override: override=" + override
                             + ", networkTypes=" + Arrays.stream(networkTypes)
@@ -1219,7 +1272,7 @@
                         }
                     }
                     mDataNetworkControllerCallbacks.forEach(cb -> cb.invokeFromExecutor(
-                            () -> cb.onSubscriptionPlanOverride()));
+                            cb::onSubscriptionPlanOverride));
                 } else {
                     loge("Unknown override mask: " + overrideMask);
                 }
@@ -1358,28 +1411,6 @@
             // When reaching here, it means this data network can satisfy all the network requests.
             logv("Found a compatible data network " + dataNetwork + ". Attaching "
                     + requestList);
-
-            // If WLAN preferred, see whether a more suitable data profile shall be used to satisfy
-            // a short-lived request that doesn't perform handover.
-            int capability = requestList.getFirst().getApnTypeNetworkCapability();
-            int preferredTransport = mAccessNetworksManager
-                    .getPreferredTransportByNetworkCapability(capability);
-            if (capability == NetworkCapabilities.NET_CAPABILITY_MMS
-                    && preferredTransport != dataNetwork.getTransport()
-                    && preferredTransport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
-                DataProfile candidate = mDataProfileManager
-                        .getDataProfileForNetworkRequest(requestList.getFirst(),
-                                TelephonyManager.NETWORK_TYPE_IWLAN,
-                                mServiceState.isUsingNonTerrestrialNetwork(),
-                                isEsimBootStrapProvisioningActivated(),
-                                false/*ignorePermanentFailure*/);
-                if (candidate != null && !dataNetwork.getDataProfile().equals(candidate)) {
-                    logv("But skipped because found better data profile " + candidate
-                            + DataUtils.networkCapabilityToString(capability) + " preferred on "
-                            + AccessNetworkConstants.transportTypeToString(preferredTransport));
-                    continue;
-                }
-            }
             return dataNetwork.attachNetworkRequests(requestList);
         }
         return false;
@@ -1451,11 +1482,23 @@
      * still allowed in this case.
      */
     public boolean isInternetDataAllowed(boolean ignoreExistingNetworks) {
+        return !getInternetEvaluation(ignoreExistingNetworks).containsDisallowedReasons();
+    }
+
+    /**
+     * @param ignoreExistingNetworks {@code true} to skip the existing network check.
+     * @return The internet evaluation result.
+     * For example, if SIM is absent, or airplane mode is on, then data is NOT allowed.
+     * This API does not reflect the currently internet data network status. It's possible there is
+     * no internet data due to weak cellular signal or network side issue, but internet data is
+     * still allowed in this case.
+     */
+    @NonNull
+    public DataEvaluation getInternetEvaluation(boolean ignoreExistingNetworks) {
         TelephonyNetworkRequest internetRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                        .build(), mPhone);
+                        .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()
@@ -1463,7 +1506,7 @@
                 && mDataNetworkList.stream().anyMatch(
                         dataNetwork -> internetRequest.canBeSatisfiedBy(
                                 dataNetwork.getNetworkCapabilities()))) {
-            return true;
+            return new DataEvaluation(DataEvaluationReason.EXTERNAL_QUERY);
         }
 
         // If no existing network can satisfy the request, then check if we can possibly setup
@@ -1471,17 +1514,19 @@
 
         DataEvaluation evaluation = evaluateNetworkRequest(internetRequest,
                 DataEvaluationReason.EXTERNAL_QUERY);
-        if (evaluation.containsOnly(DataDisallowedReason.ONLY_ALLOWED_SINGLE_NETWORK)) {
+        if (evaluation.containsOnly(DataDisallowedReason.ONLY_ALLOWED_SINGLE_NETWORK)
+                && internetRequest.getPriority() > mDataNetworkList.stream()
+                .map(DataNetwork::getPriority)
+                .max(Comparator.comparing(Integer::valueOf))
+                .orElse(0)) {
             // If the only failed reason is only single network allowed, then check if the request
             // can trump the current network.
-            return internetRequest.getPriority() > mDataNetworkList.stream()
-                    .map(DataNetwork::getPriority)
-                    .max(Comparator.comparing(Integer::valueOf))
-                    .orElse(0);
+            evaluation.addDataAllowedReason(DataAllowedReason.NORMAL);
         }
-        return !evaluation.containsDisallowedReasons();
+        return evaluation;
     }
 
+
     /**
      * @return {@code true} if internet is unmetered.
      */
@@ -1510,12 +1555,12 @@
      * @return List of the reasons why internet data is not allowed. An empty list if internet
      * is allowed.
      */
-    public @NonNull List<DataDisallowedReason> getInternetDataDisallowedReasons() {
+    @NonNull
+    public List<DataDisallowedReason> getInternetDataDisallowedReasons() {
         TelephonyNetworkRequest internetRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         DataEvaluation evaluation = evaluateNetworkRequest(internetRequest,
                 DataEvaluationReason.EXTERNAL_QUERY);
         return evaluation.getDataDisallowedReasons();
@@ -1529,18 +1574,26 @@
      * @param reason The reason for evaluation.
      * @return The data evaluation result.
      */
-    private @NonNull DataEvaluation evaluateNetworkRequest(
+    @NonNull
+    private DataEvaluation evaluateNetworkRequest(
             @NonNull TelephonyNetworkRequest networkRequest, DataEvaluationReason reason) {
         DataEvaluation evaluation = new DataEvaluation(reason);
         int transport = mAccessNetworksManager.getPreferredTransportByNetworkCapability(
                 networkRequest.getApnTypeNetworkCapability());
 
+        // Check if the request can be satisfied by cellular network or satellite network.
+        if (mFeatureFlags.satelliteInternet()
+                && !canConnectivityTransportSatisfyNetworkRequest(networkRequest, transport)) {
+            evaluation.addDataDisallowedReason(
+                    DataDisallowedReason.DATA_NETWORK_TRANSPORT_NOT_ALLOWED);
+        }
+
         // Bypass all checks for emergency network request.
         if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
             DataProfile emergencyProfile = mDataProfileManager.getDataProfileForNetworkRequest(
                     networkRequest, getDataNetworkType(transport),
                     mServiceState.isUsingNonTerrestrialNetwork(),
-                    isEsimBootStrapProvisioningActivated(), true);
+                    false /*isEsimBootStrapProvisioning*/, true);
 
             // Check if the profile is being throttled.
             if (mDataConfigManager.shouldHonorRetryTimerForEmergencyNetworkRequest()
@@ -1549,14 +1602,13 @@
                 evaluation.addDataDisallowedReason(DataDisallowedReason.DATA_THROTTLED);
                 log("Emergency network request is throttled by the previous setup data "
                             + "call response.");
-                log(evaluation.toString());
-                networkRequest.setEvaluation(evaluation);
-                return evaluation;
             }
 
-            evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_REQUEST);
-            if (emergencyProfile != null) {
-                evaluation.setCandidateDataProfile(emergencyProfile);
+            if (!evaluation.containsDisallowedReasons()) {
+                evaluation.addDataAllowedReason(DataAllowedReason.EMERGENCY_REQUEST);
+                if (emergencyProfile != null) {
+                    evaluation.setCandidateDataProfile(emergencyProfile);
+                }
             }
             networkRequest.setEvaluation(evaluation);
             log(evaluation.toString());
@@ -1617,7 +1669,7 @@
         }
 
         // Check if there are pending tear down all networks request.
-        if (mPendingTearDownAllNetworks) {
+        if (mPhone.getServiceStateTracker().isPendingRadioPowerOffAfterDataOff()) {
             evaluation.addDataDisallowedReason(DataDisallowedReason.PENDING_TEAR_DOWN_ALL);
         }
 
@@ -1645,11 +1697,6 @@
             evaluation.addDataDisallowedReason(DataDisallowedReason.CDMA_EMERGENCY_CALLBACK_MODE);
         }
 
-        // Check whether data is disallowed while using satellite
-        if (isDataDisallowedDueToSatellite(networkRequest.getCapabilities())) {
-            evaluation.addDataDisallowedReason(DataDisallowedReason.SERVICE_OPTION_NOT_SUPPORTED);
-        }
-
         // Check if only one data network is allowed.
         if (isOnlySingleDataNetworkAllowed(transport)
                 && !hasCapabilityExemptsFromSinglePdnRule(networkRequest.getCapabilities())) {
@@ -1746,7 +1793,7 @@
         networkRequest.setEvaluation(evaluation);
         // EXTERNAL_QUERY generates too many log spam.
         if (reason != DataEvaluationReason.EXTERNAL_QUERY) {
-            log(evaluation.toString() + ", network type="
+            log(evaluation + ", network type="
                     + TelephonyManager.getNetworkTypeName(getDataNetworkType(transport))
                     + ", reg state="
                     + NetworkRegistrationInfo.registrationStateToString(
@@ -1761,7 +1808,8 @@
      *  - At evaluation network request and evaluation data network determines, if
      *    bootstrap sim current data usage reached bootstrap sim max data limit allowed set
      *    at {@link DataConfigManager#getEsimBootStrapMaxDataLimitBytes()}
-     *  - Query the current data usage at {@link #getDataUsage()}
+     *  - Query the current data usage at {@link #getDataUsage()}, if last data usage query guarding
+     *    interval as expired.
      *
      * @return true, if bootstrap sim data limit is reached
      *         else false, if bootstrap sim max data limit allowed set is -1(Unlimited) or current
@@ -1776,13 +1824,15 @@
             return false;
         }
 
-        log("current bootstrap sim data Usage: " + mBootStrapSimTotalDataUsageBytes);
-        if (mBootStrapSimTotalDataUsageBytes >= esimBootStrapMaxDataLimitBytes) {
-            return true;
-        } else {
+        if (mBootStrapSimTotalDataUsageBytes < esimBootStrapMaxDataLimitBytes
+                && (mBootstrapSimLastDataUsageQueryTime == 0
+                || SystemClock.elapsedRealtime() - mBootstrapSimLastDataUsageQueryTime
+                > GUARD_TIMER_INTERVAL_TO_QUERY_DATA_USAGE_API_STATS_MILLIS)) {
             mBootStrapSimTotalDataUsageBytes = getDataUsage();
-            return mBootStrapSimTotalDataUsageBytes >= esimBootStrapMaxDataLimitBytes;
+            log("current bootstrap sim data usage: " + mBootStrapSimTotalDataUsageBytes);
+            mBootstrapSimLastDataUsageQueryTime =  SystemClock.elapsedRealtime();
         }
+        return mBootStrapSimTotalDataUsageBytes >= esimBootStrapMaxDataLimitBytes;
     }
 
     /**
@@ -1802,6 +1852,8 @@
 
             if (!TextUtils.isEmpty(subscriberId)) {
                 builder.setSubscriberIds(Set.of(subscriberId));
+                // Consider data usage calculation of only metered capabilities / data network
+                builder.setMeteredness(android.net.NetworkStats.METERED_YES);
                 NetworkTemplate template = builder.build();
                 final NetworkStats.Bucket ret = networkStatsManager
                         .querySummaryForDevice(template, 0L, System.currentTimeMillis());
@@ -1815,14 +1867,15 @@
      * @return The grouped unsatisfied network requests. The network requests that have the same
      * network capabilities is grouped into one {@link NetworkRequestList}.
      */
-    private @NonNull List<NetworkRequestList> getGroupedUnsatisfiedNetworkRequests() {
+    @NonNull
+    private List<NetworkRequestList> getGroupedUnsatisfiedNetworkRequests() {
         NetworkRequestList networkRequestList = new NetworkRequestList();
         for (TelephonyNetworkRequest networkRequest : mAllNetworkRequestList) {
             if (networkRequest.getState() == TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED) {
                 networkRequestList.add(networkRequest);
             }
         }
-        return DataUtils.getGroupedNetworkRequestList(networkRequestList);
+        return DataUtils.getGroupedNetworkRequestList(networkRequestList, mFeatureFlags);
     }
 
     /**
@@ -1836,8 +1889,7 @@
         log("Re-evaluating " + networkRequestLists.stream().mapToInt(List::size).sum()
                 + " unsatisfied network requests in " + networkRequestLists.size()
                 + " groups, " + networkRequestLists.stream().map(
-                        requestList -> DataUtils.networkCapabilitiesToString(
-                                requestList.get(0).getCapabilities()))
+                        NetworkRequestList::toStringSimplified)
                 .collect(Collectors.joining(", ")) + " due to " + reason);
 
         // Second, see if any existing network can satisfy those network requests.
@@ -1870,7 +1922,8 @@
      *
      * @return The data evaluation result.
      */
-    private @NonNull DataEvaluation evaluateDataNetwork(@NonNull DataNetwork dataNetwork,
+    @NonNull
+    private DataEvaluation evaluateDataNetwork(@NonNull DataNetwork dataNetwork,
             @NonNull DataEvaluationReason reason) {
         DataEvaluation evaluation = new DataEvaluation(reason);
         // Bypass all checks for emergency data network.
@@ -1891,10 +1944,16 @@
             evaluation.addDataDisallowedReason(DataDisallowedReason.CDMA_EMERGENCY_CALLBACK_MODE);
         }
 
-        // Check whether data is disallowed while using satellite
-        if (isDataDisallowedDueToSatellite(dataNetwork.getNetworkCapabilities()
-                .getCapabilities())) {
-            evaluation.addDataDisallowedReason(DataDisallowedReason.SERVICE_OPTION_NOT_SUPPORTED);
+        // If the network is satellite, then the network must be restricted.
+        if (mFeatureFlags.satelliteInternet()) {
+            // The IWLAN data network should remain intact even when satellite is connected.
+            if (dataNetwork.getTransport() != AccessNetworkConstants.TRANSPORT_TYPE_WLAN
+                    && mServiceState.isUsingNonTerrestrialNetwork() != dataNetwork.isSatellite()) {
+                // Since we don't support satellite/cellular network handover, we should always
+                // tear down the network when transport changes.
+                evaluation.addDataDisallowedReason(
+                        DataDisallowedReason.DATA_NETWORK_TRANSPORT_NOT_ALLOWED);
+            }
         }
 
         // Check whether data limit reached for bootstrap sim, else re-evaluate based on the timer
@@ -1908,7 +1967,7 @@
                 if (!hasMessages(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS)) {
                     sendMessageDelayed(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
                             DataEvaluationReason.CHECK_DATA_USAGE),
-                            REEVALUATE_BOOTSTRAP_SIM_DATA_USAGE_MILLIS);
+                            mDataConfigManager.getReevaluateBootstrapSimDataUsageMillis());
                 } else {
                     log("skip scheduling evaluating existing data networks since already"
                             + "scheduled");
@@ -2082,15 +2141,97 @@
     }
 
     /**
-     * tethering and enterprise capabilities are not respected as restricted requests. For a request
-     * with these capabilities, any soft disallowed reasons are honored.
+     * Check if the transport from connectivity service can satisfy the network request. Note the
+     * transport here is connectivity service's transport (Wifi, cellular, satellite, etc..), not
+     * the widely used {@link AccessNetworkConstants#TRANSPORT_TYPE_WLAN WLAN},
+     * {@link AccessNetworkConstants#TRANSPORT_TYPE_WWAN WWAN} transport in telephony.
+     *
+     * @param networkRequest Network request
+     * @param transport The preferred transport type for the request. The transport here is
+     * WWAN/WLAN.
+     * @return {@code true} if the connectivity transport can satisfy the network request, otherwise
+     * {@code false}.
+     */
+    private boolean canConnectivityTransportSatisfyNetworkRequest(
+            @NonNull TelephonyNetworkRequest networkRequest, @TransportType int transport) {
+        // Check if this is a IWLAN network request.
+        if (transport == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+            // If the request would result in bringing up network on IWLAN, then no
+            // need to check if the device is using satellite network.
+            return true;
+        }
+
+        // When the device is on satellite, only restricted/constrained network request can request
+        // network.
+        if (mServiceState.isUsingNonTerrestrialNetwork() && networkRequest.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) {
+            switch (mDataConfigManager.getSatelliteDataSupportMode()) {
+                case CarrierConfigManager.SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED -> {
+                    return false;
+                }
+                case CarrierConfigManager.SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED -> {
+                    try {
+                        if (networkRequest.hasCapability(DataUtils
+                                .NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)) {
+                            return false;
+                        }
+                    } catch (Exception ignored) { }
+                }
+                // default case CarrierConfigManager.SATELLITE_DATA_SUPPORT_ALL
+            }
+        }
+
+        // If the network request does not specify cellular or satellite, then it can be
+        // satisfied when the device is either on cellular ot satellite.
+        if (!networkRequest.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+                && !networkRequest.hasTransport(NetworkCapabilities.TRANSPORT_SATELLITE)) {
+            return true;
+        }
+
+        // As a short term solution, allowing some networks to be always marked as cellular
+        // transport if certain capabilities are in the network request.
+        if (networkRequest.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) && Arrays.stream(
+                        networkRequest.getCapabilities())
+                .anyMatch(mDataConfigManager.getForcedCellularTransportCapabilities()::contains)) {
+            return true;
+        }
+
+        // If the network is cellular, then the request must specify cellular transport. Or if the
+        // the network is satellite, then the request must specify satellite transport and
+        // restricted.
+        return (mServiceState.isUsingNonTerrestrialNetwork()
+                && networkRequest.hasTransport(NetworkCapabilities.TRANSPORT_SATELLITE))
+                || (!mServiceState.isUsingNonTerrestrialNetwork()
+                        && networkRequest.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR));
+    }
+
+    /**
+     * Check if a network request should be treated as a valid restricted network request that
+     * can bypass soft disallowed reasons, for example, mobile data off.
+     *
      * @param networkRequest The network request to evaluate.
-     * @return {@code true} if the request doesn't contain any exceptional capabilities, its
-     * restricted capability, if any, is respected.
+     * @return {@code true} if the request can be considered as a valid restricted network request
+     * that can bypass any soft disallowed reasons, otherwise {@code false}.
      */
     private boolean isValidRestrictedRequest(@NonNull TelephonyNetworkRequest networkRequest) {
-        return !(networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
-                || networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE));
+
+        if (!mFeatureFlags.satelliteInternet()) {
+            return !(networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
+                    || networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE));
+        } else {
+            // tethering, enterprise and mms with restricted capabilities always honor soft
+            // disallowed reasons and not respected as restricted request
+            if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
+                    || networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
+                    || networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+                return false;
+            }
+            // When the device is on satellite, internet with restricted capabilities always honor
+            // soft disallowed reasons and not respected as restricted request
+            return !(mServiceState.isUsingNonTerrestrialNetwork()
+                    && networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+
+        }
     }
 
     /**
@@ -2124,7 +2265,8 @@
      *
      * @see CarrierConfigManager#KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY
      */
-    private @NonNull DataEvaluation evaluateDataNetworkHandover(@NonNull DataNetwork dataNetwork) {
+    @NonNull
+    private DataEvaluation evaluateDataNetworkHandover(@NonNull DataNetwork dataNetwork) {
         DataEvaluation dataEvaluation = new DataEvaluation(DataEvaluationReason.DATA_HANDOVER);
         if (!dataNetwork.isConnecting() && !dataNetwork.isConnected()) {
             dataEvaluation.addDataDisallowedReason(DataDisallowedReason.ILLEGAL_STATE);
@@ -2182,10 +2324,7 @@
                     sourceNetworkType);
             NetworkRegistrationInfo nri = mServiceState.getNetworkRegistrationInfo(
                     NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
-            boolean isWwanInService = false;
-            if (nri != null && nri.isInService()) {
-                isWwanInService = true;
-            }
+            boolean isWwanInService = nri != null && nri.isInService();
             // If WWAN is inService, use the real roaming state reported by modem instead of
             // using the overridden roaming state, otherwise get last known roaming state stored
             // in data network.
@@ -2247,7 +2386,8 @@
      * {@link #evaluateDataNetwork(DataNetwork, DataEvaluationReason)}.
      * @return The tear down reason.
      */
-    private static @TearDownReason int getTearDownReason(@NonNull DataEvaluation dataEvaluation) {
+    @TearDownReason
+    private static int getTearDownReason(@NonNull DataEvaluation dataEvaluation) {
         if (dataEvaluation.containsDisallowedReasons()) {
             switch (dataEvaluation.getDataDisallowedReasons().get(0)) {
                 case DATA_DISABLED:
@@ -2300,6 +2440,8 @@
                     return DataNetwork.TEAR_DOWN_REASON_HANDOVER_FAILED;
                 case DATA_LIMIT_REACHED:
                     return DataNetwork.TEAR_DOWN_REASON_DATA_LIMIT_REACHED;
+                case DATA_NETWORK_TRANSPORT_NOT_ALLOWED:
+                    return DataNetwork.TEAR_DOWN_REASON_DATA_NETWORK_TRANSPORT_NOT_ALLOWED;
             }
         }
         return DataNetwork.TEAR_DOWN_REASON_NONE;
@@ -2446,14 +2588,14 @@
         RegistrationManager.RegistrationCallback callback =
                 new RegistrationManager.RegistrationCallback() {
                     @Override
-                    public void onRegistered(ImsRegistrationAttributes attributes) {
+                    public void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
                         log("IMS " + DataUtils.imsFeatureToString(imsFeature)
                                 + " registered. Attributes=" + attributes);
                         mRegisteredImsFeatures.add(imsFeature);
                     }
 
                     @Override
-                    public void onUnregistered(ImsReasonInfo info) {
+                    public void onUnregistered(@NonNull ImsReasonInfo info) {
                         log("IMS " + DataUtils.imsFeatureToString(imsFeature)
                                 + " deregistered. Info=" + info);
                         mRegisteredImsFeatures.remove(imsFeature);
@@ -2661,8 +2803,8 @@
      * @param dataProfile The data profile.
      * @return The network requests list.
      */
-    private @NonNull NetworkRequestList findSatisfiableNetworkRequests(
-            @NonNull DataProfile dataProfile) {
+    @NonNull
+    private NetworkRequestList findSatisfiableNetworkRequests(@NonNull DataProfile dataProfile) {
         return new NetworkRequestList(mAllNetworkRequestList.stream()
                 .filter(request -> request.getState()
                         == TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED)
@@ -2839,7 +2981,6 @@
         mDataNetworkList.remove(dataNetwork);
         trackSetupDataCallFailure(dataNetwork.getTransport(), cause);
         if (mAnyDataNetworkExisting && mDataNetworkList.isEmpty()) {
-            mPendingTearDownAllNetworks = false;
             mAnyDataNetworkExisting = false;
             mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
                     () -> callback.onAnyDataNetworkExistingChanged(mAnyDataNetworkExisting)));
@@ -2940,7 +3081,7 @@
         if (isEsimBootStrapProvisioningActivated()) {
             sendMessageDelayed(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
                     DataEvaluationReason.CHECK_DATA_USAGE),
-                    REEVALUATE_BOOTSTRAP_SIM_DATA_USAGE_MILLIS);
+                    mDataConfigManager.getReevaluateBootstrapSimDataUsageMillis());
         }
     }
 
@@ -2962,8 +3103,18 @@
             List<NetworkRequestList> groupRequestLists = getGroupedUnsatisfiedNetworkRequests();
             dataSetupRetryEntry.networkRequestList.stream()
                     .filter(request -> groupRequestLists.stream()
-                            .anyMatch(groupRequestList -> groupRequestList
-                                    .get(request.getCapabilities()) != null))
+                            .anyMatch(groupRequestList -> {
+                                // The unsatisfied request has all the requested capabilities.
+                                if (groupRequestList.get(request.getCapabilities()) == null) {
+                                    return false;
+                                }
+                                TelephonyNetworkRequest leading = groupRequestList.getFirst();
+                                // The unsatisfied request covers all the requested transports.
+                                return leading.getTransportTypes().length == 0
+                                        || request.getTransportTypes().length == 0
+                                        || Arrays.stream(request.getTransportTypes())
+                                        .allMatch(leading::hasTransport);
+                            }))
                     .forEach(requestList::add);
         }
         if (requestList.isEmpty()) {
@@ -3107,7 +3258,13 @@
             return;
         }
 
-        if (dataNetwork.isInternetSupported()) {
+        // Only track the networks that require validation.
+        // The criteria is base on NetworkMonitorUtils.java.
+        NetworkCapabilities capabilities = dataNetwork.getNetworkCapabilities();
+        if (capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)) {
             if (status == NetworkAgent.VALIDATION_STATUS_NOT_VALID
                     && (dataNetwork.getCurrentState() == null || dataNetwork.isDisconnected())) {
                 log("Ignoring invalid validation status for disconnected DataNetwork");
@@ -3164,7 +3321,6 @@
 
         if (mAnyDataNetworkExisting && mDataNetworkList.isEmpty()) {
             log("All data networks disconnected now.");
-            mPendingTearDownAllNetworks = false;
             mAnyDataNetworkExisting = false;
             mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
                     () -> callback.onAnyDataNetworkExistingChanged(mAnyDataNetworkExisting)));
@@ -3590,7 +3746,7 @@
             return true;
         }
 
-        if (!oldNri.isNonTerrestrialNetwork() && newNri.isNonTerrestrialNetwork()) {
+        if (oldNri.isNonTerrestrialNetwork() != newNri.isNonTerrestrialNetwork()) {
             return true;
         }
 
@@ -3598,17 +3754,13 @@
         DataSpecificRegistrationInfo newDsri = newNri.getDataSpecificInfo();
 
         if (newDsri == null) return false;
-        if ((oldDsri == null || oldDsri.getVopsSupportInfo() == null
+        // If previously VoPS was supported (or does not exist), and now the network reports
+        // VoPS not supported, we should evaluate existing data networks to see if they need
+        // to be torn down.
+        return (oldDsri == null || oldDsri.getVopsSupportInfo() == null
                 || oldDsri.getVopsSupportInfo().isVopsSupported())
                 && (newDsri.getVopsSupportInfo() != null && !newDsri.getVopsSupportInfo()
-                .isVopsSupported())) {
-            // If previously VoPS was supported (or does not exist), and now the network reports
-            // VoPS not supported, we should evaluate existing data networks to see if they need
-            // to be torn down.
-            return true;
-        }
-
-        return false;
+                .isVopsSupported());
     }
 
     /**
@@ -3647,7 +3799,7 @@
             return true;
         }
 
-        if (oldSS.isUsingNonTerrestrialNetwork() && !newSS.isUsingNonTerrestrialNetwork()) {
+        if (oldSS.isUsingNonTerrestrialNetwork() != newSS.isUsingNonTerrestrialNetwork()) {
             return true;
         }
 
@@ -3655,17 +3807,13 @@
         DataSpecificRegistrationInfo newDsri = newPsNri.getDataSpecificInfo();
 
         if (oldDsri == null) return false;
-        if ((newDsri == null || newDsri.getVopsSupportInfo() == null
+        // If previously VoPS was not supported, and now the network reports
+        // VoPS supported (or does not report), we should evaluate the unsatisfied network
+        // request to see if the can be satisfied again.
+        return (newDsri == null || newDsri.getVopsSupportInfo() == null
                 || newDsri.getVopsSupportInfo().isVopsSupported())
                 && (oldDsri.getVopsSupportInfo() != null && !oldDsri.getVopsSupportInfo()
-                .isVopsSupported())) {
-            // If previously VoPS was not supported, and now the network reports
-            // VoPS supported (or does not report), we should evaluate the unsatisfied network
-            // request to see if the can be satisfied again.
-            return true;
-        }
-
-        return false;
+                .isVopsSupported());
     }
 
     /**
@@ -3776,28 +3924,32 @@
     /**
      * @return Data config manager instance.
      */
-    public @NonNull DataConfigManager getDataConfigManager() {
+    @NonNull
+    public DataConfigManager getDataConfigManager() {
         return mDataConfigManager;
     }
 
     /**
      * @return Data profile manager instance.
      */
-    public @NonNull DataProfileManager getDataProfileManager() {
+    @NonNull
+    public DataProfileManager getDataProfileManager() {
         return mDataProfileManager;
     }
 
     /**
      * @return Data settings manager instance.
      */
-    public @NonNull DataSettingsManager getDataSettingsManager() {
+    @NonNull
+    public DataSettingsManager getDataSettingsManager() {
         return mDataSettingsManager;
     }
 
     /**
      * @return Data retry manager instance.
      */
-    public @NonNull DataRetryManager getDataRetryManager() {
+    @NonNull
+    public DataRetryManager getDataRetryManager() {
         return mDataRetryManager;
     }
 
@@ -3805,7 +3957,8 @@
      * @return The list of SubscriptionPlans
      */
     @VisibleForTesting
-    public @NonNull List<SubscriptionPlan> getSubscriptionPlans() {
+    @NonNull
+    public List<SubscriptionPlan> getSubscriptionPlans() {
         return mSubscriptionPlans;
     }
 
@@ -3813,7 +3966,9 @@
      * @return The set of network types an unmetered override applies to
      */
     @VisibleForTesting
-    public @NonNull @NetworkType Set<Integer> getUnmeteredOverrideNetworkTypes() {
+    @NonNull
+    @NetworkType
+    public Set<Integer> getUnmeteredOverrideNetworkTypes() {
         return mUnmeteredOverrideNetworkTypes;
     }
 
@@ -3821,7 +3976,9 @@
      * @return The set of network types a congested override applies to
      */
     @VisibleForTesting
-    public @NonNull @NetworkType Set<Integer> getCongestedOverrideNetworkTypes() {
+    @NonNull
+    @NetworkType
+    public Set<Integer> getCongestedOverrideNetworkTypes() {
         return mCongestedOverrideNetworkTypes;
     }
 
@@ -3831,7 +3988,8 @@
      * @param transport The transport.
      * @return The current network type.
      */
-    private @NetworkType int getDataNetworkType(@TransportType int transport) {
+    @NetworkType
+    private int getDataNetworkType(@TransportType int transport) {
         NetworkRegistrationInfo nri = mServiceState.getNetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, transport);
         if (nri != null) {
@@ -3847,8 +4005,8 @@
      * @param transport The transport.
      * @return The registration state.
      */
-    private @RegistrationState int getDataRegistrationState(@NonNull ServiceState ss,
-            @TransportType int transport) {
+    @RegistrationState
+    private int getDataRegistrationState(@NonNull ServiceState ss, @TransportType int transport) {
         NetworkRegistrationInfo nri = ss.getNetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, transport);
         if (nri != null) {
@@ -3860,7 +4018,8 @@
     /**
      * @return The data activity. Note this is only updated when screen is on.
      */
-    public @DataActivityType int getDataActivity() {
+    @DataActivityType
+    public int getDataActivity() {
         return mDataActivity;
     }
 
@@ -3898,14 +4057,13 @@
      *
      * @param reason The reason to tear down.
      */
-    public void onTearDownAllDataNetworks(@TearDownReason int reason) {
+    private void onTearDownAllDataNetworks(@TearDownReason int reason) {
         log("onTearDownAllDataNetworks: reason=" + DataNetwork.tearDownReasonToString(reason));
         if (mDataNetworkList.isEmpty()) {
             log("tearDownAllDataNetworks: No pending networks. All disconnected now.");
             return;
         }
 
-        mPendingTearDownAllNetworks = true;
         for (DataNetwork dataNetwork : mDataNetworkList) {
             if (!dataNetwork.isDisconnecting()) {
                 tearDownGracefully(dataNetwork, reason);
@@ -4007,14 +4165,16 @@
      *
      * @return The data network state.
      */
-    public @DataState int getInternetDataNetworkState() {
+    @DataState
+    public int getInternetDataNetworkState() {
         return mInternetDataNetworkState;
     }
 
     /**
      * @return List of bound data service packages name on WWAN and WLAN.
      */
-    public @NonNull List<String> getDataServicePackages() {
+    @NonNull
+    public List<String> getDataServicePackages() {
         List<String> packages = new ArrayList<>();
         for (int i = 0; i < mDataServiceManagers.size(); i++) {
             packages.add(mDataServiceManagers.valueAt(i).getDataServicePackageName());
@@ -4023,44 +4183,9 @@
     }
 
     /**
-     * Check whether data is disallowed while using satellite
-     * @param capabilities An array of the NetworkCapabilities to be checked
-     * @return {@code true} if the capabilities contain any capability that are restricted
-     * while using satellite else {@code false}
-     */
-    private boolean isDataDisallowedDueToSatellite(@NetCapability int[] capabilities) {
-        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
-            return false;
-        }
-
-        if (!mServiceState.isUsingNonTerrestrialNetwork()) {
-            // Device is not connected to satellite
-            return false;
-        }
-
-        Set<Integer> restrictedCapabilities = Set.of(NetworkCapabilities.NET_CAPABILITY_INTERNET);
-        if (Arrays.stream(capabilities).noneMatch(restrictedCapabilities::contains)) {
-            // Only internet data disallowed while using satellite
-            return false;
-        }
-
-        for (NetworkRegistrationInfo nri : mServiceState.getNetworkRegistrationInfoList()) {
-            if (nri.isNonTerrestrialNetwork()
-                    && nri.getAvailableServices().contains(
-                            NetworkRegistrationInfo.SERVICE_TYPE_DATA)) {
-                // Data is supported while using satellite
-                return false;
-            }
-        }
-
-        // Data is disallowed while using satellite
-        return true;
-    }
-
-    /**
      * Request network validation.
-     *
-     * Nnetwork validation request is sent to the DataNetwork that matches the network capability
+     * <p>
+     * Network validation request is sent to the DataNetwork that matches the network capability
      * in the list of DataNetwork owned by the DNC.
      *
      * @param capability network capability {@link NetCapability}
diff --git a/src/java/com/android/internal/telephony/data/DataProfileManager.java b/src/java/com/android/internal/telephony/data/DataProfileManager.java
index 51fc71b..c50d50a 100644
--- a/src/java/com/android/internal/telephony/data/DataProfileManager.java
+++ b/src/java/com/android/internal/telephony/data/DataProfileManager.java
@@ -45,6 +45,7 @@
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.LocalLog;
+import android.util.LruCache;
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
@@ -81,42 +82,51 @@
     private final LocalLog mLocalLog = new LocalLog(128);
 
     /** Data network controller. */
-    private final @NonNull DataNetworkController mDataNetworkController;
+    @NonNull
+    private final DataNetworkController mDataNetworkController;
 
     /** Data config manager. */
-    private final @NonNull DataConfigManager mDataConfigManager;
+    @NonNull
+    private final DataConfigManager mDataConfigManager;
 
     /** Cellular data service. */
-    private final @NonNull DataServiceManager mWwanDataServiceManager;
+    @NonNull
+    private final DataServiceManager mWwanDataServiceManager;
 
     /**
      * All data profiles for the current carrier. Note only data profiles loaded from the APN
      * database will be stored here. The on-demand data profiles (generated dynamically, for
      * example, enterprise data profiles with differentiator) are not stored here.
      */
-    private final @NonNull List<DataProfile> mAllDataProfiles = new ArrayList<>();
+    @NonNull
+    private final List<DataProfile> mAllDataProfiles = new ArrayList<>();
 
     /** The data profile used for initial attach. */
-    private @Nullable DataProfile mInitialAttachDataProfile = null;
+    @Nullable
+    private DataProfile mInitialAttachDataProfile = null;
 
     /** The preferred data profile used for internet. */
-    private @Nullable DataProfile mPreferredDataProfile = null;
+    @Nullable
+    private DataProfile mPreferredDataProfile = null;
 
-    /** The last data profile that's successful for internet connection. */
-    private @Nullable DataProfile mLastInternetDataProfile = null;
+    /** The last data profile that's successful for internet connection by subscription id. */
+    @NonNull
+    private final LruCache<Integer, DataProfile> mLastInternetDataProfiles = new LruCache<>(256);
 
     /** Preferred data profile set id. */
     private int mPreferredDataProfileSetId = Telephony.Carriers.NO_APN_SET_ID;
 
     /** Data profile manager callbacks. */
-    private final @NonNull Set<DataProfileManagerCallback> mDataProfileManagerCallbacks =
-            new ArraySet<>();
+    @NonNull
+    private final Set<DataProfileManagerCallback> mDataProfileManagerCallbacks = new ArraySet<>();
 
     /** SIM state. */
-    private @SimState int mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
+    @SimState
+    private int mSimState = TelephonyManager.SIM_STATE_UNKNOWN;
 
     /** Feature flags controlling which feature is enabled. */
-    private final @NonNull FeatureFlags mFeatureFlags;
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
 
     /**
      * Data profile manager callback. This should be only used by {@link DataNetworkController}.
@@ -452,9 +462,11 @@
             }
         }
 
-        // Update a working internet data profile as a future candidate for preferred data profile
-        // after APNs are reset to default
-        mLastInternetDataProfile = defaultProfile;
+        // Update a working internet data profile by subid as a future candidate for preferred
+        // data profile after APNs are reset to default
+        if (defaultProfile != null) {
+            mLastInternetDataProfiles.put(mPhone.getSubId(), defaultProfile);
+        }
 
         // If the live default internet network is not using the preferred data profile, since
         // brought up a network means it passed sophisticated checks, update the preferred data
@@ -473,7 +485,8 @@
      *
      * @return The preferred data profile.
      */
-    private @Nullable DataProfile getPreferredDataProfileFromDb() {
+    @Nullable
+    private DataProfile getPreferredDataProfileFromDb() {
         Cursor cursor = mPhone.getContext().getContentResolver().query(
                 Uri.withAppendedPath(Telephony.Carriers.PREFERRED_APN_URI,
                         String.valueOf(mPhone.getSubId())), null, null, null,
@@ -498,7 +511,8 @@
     /**
      * @return The preferred data profile from carrier config.
      */
-    private @Nullable DataProfile getPreferredDataProfileFromConfig() {
+    @Nullable
+    private DataProfile getPreferredDataProfileFromConfig() {
         // Check if there is configured default preferred data profile.
         String defaultPreferredApn = mDataConfigManager.getDefaultPreferredApn();
         if (!TextUtils.isEmpty(defaultPreferredApn)) {
@@ -542,7 +556,8 @@
      */
     private boolean updatePreferredDataProfile() {
         DataProfile preferredDataProfile;
-        if (SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) {
+        int subId = mPhone.getSubId();
+        if (SubscriptionManager.isValidSubscriptionId(subId)) {
             preferredDataProfile = getPreferredDataProfileFromDb();
             if (preferredDataProfile == null) {
                 preferredDataProfile = getPreferredDataProfileFromConfig();
@@ -551,7 +566,8 @@
                     setPreferredDataProfile(preferredDataProfile);
                 } else {
                     preferredDataProfile = mAllDataProfiles.stream()
-                            .filter(dp -> areDataProfilesSharingApn(dp, mLastInternetDataProfile))
+                            .filter(dp -> areDataProfilesSharingApn(dp,
+                                    mLastInternetDataProfiles.get(subId)))
                             .findFirst()
                             .orElse(null);
                     if (preferredDataProfile != null) {
@@ -580,10 +596,10 @@
 
     /**
      * Update the data profile used for initial attach.
-     *
+     * <p>
      * Note that starting from Android 13 only APNs that supports "IA" type will be used for
      * initial attach. Please update APN configuration file if needed.
-     *
+     * <p>
      * Some carriers might explicitly require that using "user-added" APN for initial
      * attach. In this case, exception can be configured through
      * {@link CarrierConfigManager#KEY_ALLOWED_INITIAL_ATTACH_APN_TYPES_STRING_ARRAY}.
@@ -597,7 +613,7 @@
         // Sort the data profiles so the preferred data profile is at the beginning.
         List<DataProfile> allDataProfiles = mAllDataProfiles.stream()
                 .sorted(Comparator.comparing((DataProfile dp) -> !dp.equals(mPreferredDataProfile)))
-                .collect(Collectors.toList());
+                .toList();
         // Search in the order. "IA" type should be the first from getAllowedInitialAttachApnTypes.
         for (int apnType : mDataConfigManager.getAllowedInitialAttachApnTypes()) {
             initialAttachDataProfile = allDataProfiles.stream()
@@ -636,7 +652,8 @@
      * @param apnTypeBitmask APN type
      * @return The APN setting
      */
-    private @NonNull ApnSetting buildDefaultApnSetting(@NonNull String entry,
+    @NonNull
+    private ApnSetting buildDefaultApnSetting(@NonNull String entry,
             @NonNull String apn, @Annotation.ApnType int apnTypeBitmask) {
         return new ApnSetting.Builder()
                 .setEntryName(entry)
@@ -659,7 +676,8 @@
      * This should be set to true for condition-based retry/setup.
      * @return The data profile. {@code null} if can't find any satisfiable data profile.
      */
-    public @Nullable DataProfile getDataProfileForNetworkRequest(
+    @Nullable
+    public DataProfile getDataProfileForNetworkRequest(
             @NonNull TelephonyNetworkRequest networkRequest, @NetworkType int networkType,
             boolean isNtn, boolean isEsimBootstrapProvisioning, boolean ignorePermanentFailure) {
         ApnSetting apnSetting = null;
@@ -730,7 +748,8 @@
      * This should be set to true for condition-based retry/setup.
      * @return The APN setting. {@code null} if can't find any satisfiable data profile.
      */
-    private @Nullable ApnSetting getApnSettingForNetworkRequest(
+    @Nullable
+    private ApnSetting getApnSettingForNetworkRequest(
             @NonNull TelephonyNetworkRequest networkRequest, @NetworkType int networkType,
             boolean isNtn, boolean isEsimBootStrapProvisioning, boolean ignorePermanentFailure) {
         if (!networkRequest.hasAttribute(
@@ -793,7 +812,7 @@
             logv("Satisfied profile: " + dataProfile + ", last setup="
                     + DataUtils.elapsedTimeToString(dataProfile.getLastSetupTimestamp()));
         }
-        if (dataProfiles.size() == 0) {
+        if (dataProfiles.isEmpty()) {
             log("Can't find any data profile that can satisfy " + networkRequest);
             return null;
         }
@@ -810,16 +829,14 @@
                                 ApnSetting.INFRASTRUCTURE_SATELLITE)) {
                             return false;
                         }
-                        if (!isNtn && !dp.getApnSetting().isForInfrastructure(
-                                ApnSetting.INFRASTRUCTURE_CELLULAR)) {
-                            return false;
-                        }
+                        return isNtn || dp.getApnSetting().isForInfrastructure(
+                                ApnSetting.INFRASTRUCTURE_CELLULAR);
                     }
 
                     return true;
                 })
                 .collect(Collectors.toList());
-        if (dataProfiles.size() == 0) {
+        if (dataProfiles.isEmpty()) {
             String ntnReason = "";
             if (mFeatureFlags.carrierEnabledSatelliteFlag()) {
                 ntnReason = " and infrastructure for "
@@ -837,7 +854,7 @@
                         == Telephony.Carriers.MATCH_ALL_APN_SET_ID
                         || dp.getApnSetting().getApnSetId() == mPreferredDataProfileSetId))
                 .collect(Collectors.toList());
-        if (dataProfiles.size() == 0) {
+        if (dataProfiles.isEmpty()) {
             log("Can't find any data profile has APN set id matched. mPreferredDataProfileSetId="
                     + mPreferredDataProfileSetId);
             return null;
@@ -845,9 +862,10 @@
 
         // Check if data profiles are permanently failed.
         dataProfiles = dataProfiles.stream()
-                .filter(dp -> ignorePermanentFailure || !dp.getApnSetting().getPermanentFailed())
+                .filter(dp -> ignorePermanentFailure || (dp.getApnSetting() != null
+                        && !dp.getApnSetting().getPermanentFailed()))
                 .collect(Collectors.toList());
-        if (dataProfiles.size() == 0) {
+        if (dataProfiles.isEmpty()) {
             log("The suitable data profiles are all in permanent failed state.");
             return null;
         }
@@ -899,7 +917,7 @@
         TelephonyNetworkRequest networkRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         return getDataProfileForNetworkRequest(networkRequest, networkType,
                 mPhone.getServiceState().isUsingNonTerrestrialNetwork(),
                 mDataNetworkController.isEsimBootStrapProvisioningActivated(),
@@ -939,7 +957,8 @@
      * @param setting The Apn setting to be checked.
      */
     private void checkApnSetting(@NonNull ApnSetting setting) {
-        if (setting.canHandleType(ApnSetting.TYPE_MMS)) {
+        if (setting.canHandleType(ApnSetting.TYPE_MMS)
+                && setting.getEditedStatus() == Telephony.Carriers.UNEDITED) {
             if (setting.getMmsc() == null) {
                 reportAnomaly("MMS is supported but no MMSC configured " + setting,
                         "9af73e18-b523-4dc5-adab-19d86c6a3685");
@@ -965,7 +984,7 @@
     private void checkDataProfiles(List<DataProfile> profiles) {
         for (int i = 0; i < profiles.size(); i++) {
             ApnSetting a = profiles.get(i).getApnSetting();
-            if (a == null) continue;
+            if (a == null || a.getEditedStatus() != Telephony.Carriers.UNEDITED) continue;
             if (// Lingering network is not the default and doesn't cover all the regular networks
                     (int) TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN
                     != a.getLingeringNetworkTypeBitmask()
@@ -979,23 +998,6 @@
                                 a.getLingeringNetworkTypeBitmask()),
                         "9af73e18-b523-4dc5-adab-4bb24355d838");
             }
-            for (int j = i + 1; j < profiles.size(); j++) {
-                ApnSetting b = profiles.get(j).getApnSetting();
-                if (b == null) continue;
-                String apnNameA = a.getApnName();
-                String apnNameB = b.getApnName();
-                if (TextUtils.equals(apnNameA, apnNameB)
-                        // TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN means all network types
-                        && (a.getNetworkTypeBitmask()
-                        == (int) TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN
-                        || b.getNetworkTypeBitmask()
-                        == (int) TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN
-                        || (a.getNetworkTypeBitmask() & b.getNetworkTypeBitmask()) != 0)) {
-                    reportAnomaly("Found overlapped network type under the APN name "
-                                    + a.getApnName(),
-                            "9af73e18-b523-4dc5-adab-4bb24555d839");
-                }
-            }
         }
     }
 
@@ -1007,7 +1009,8 @@
      *
      * @return The merged data profile. {@code null} if merging is not possible.
      */
-    private static @Nullable DataProfile mergeDataProfiles(
+    @Nullable
+    private static DataProfile mergeDataProfiles(
             @NonNull DataProfile dp1, @NonNull DataProfile dp2) {
         Objects.requireNonNull(dp1);
         Objects.requireNonNull(dp2);
@@ -1106,10 +1109,6 @@
      * @return {@code true} if the provided data profile can be still used in current environment.
      */
     public boolean isDataProfileCompatible(@NonNull DataProfile dataProfile) {
-        if (dataProfile == null) {
-            return false;
-        }
-
         if (dataProfile.getApnSetting() == null && dataProfile.getTrafficDescriptor() != null) {
             // A traffic descriptor only data profile can be always used. Traffic descriptors are
             // always generated on the fly instead loaded from the database.
@@ -1224,7 +1223,10 @@
         pw.println("Preferred data profile from db=" + getPreferredDataProfileFromDb());
         pw.println("Preferred data profile from config=" + getPreferredDataProfileFromConfig());
         pw.println("Preferred data profile set id=" + mPreferredDataProfileSetId);
-        pw.println("Last internet data profile=" + mLastInternetDataProfile);
+        pw.println("Last internet data profile for=");
+        pw.increaseIndent();
+        mLastInternetDataProfiles.snapshot().forEach((key, value) -> pw.println(key + ":" + value));
+        pw.decreaseIndent();
         pw.println("Initial attach data profile=" + mInitialAttachDataProfile);
         pw.println("isTetheringDataProfileExisting=" + isTetheringDataProfileExisting(
                 TelephonyManager.NETWORK_TYPE_LTE));
diff --git a/src/java/com/android/internal/telephony/data/DataRetryManager.java b/src/java/com/android/internal/telephony/data/DataRetryManager.java
index 5933463..d35e9e8 100644
--- a/src/java/com/android/internal/telephony/data/DataRetryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataRetryManager.java
@@ -150,53 +150,67 @@
     private static final int RESET_REASON_TAC_CHANGED = 6;
 
     /** The phone instance. */
-    private final @NonNull Phone mPhone;
+    @NonNull
+    private final Phone mPhone;
 
     /** Featureflags. */
-    private final @NonNull FeatureFlags mFlags;
+    @NonNull
+    private final FeatureFlags mFlags;
 
     /** The RIL instance. */
-    private final @NonNull CommandsInterface mRil;
+    @NonNull
+    private final CommandsInterface mRil;
 
     /** Logging tag. */
-    private final @NonNull String mLogTag;
+    @NonNull
+    private final String mLogTag;
 
     /** Local log. */
-    private final @NonNull LocalLog mLocalLog = new LocalLog(128);
+    @NonNull
+    private final LocalLog mLocalLog = new LocalLog(128);
 
     /** Alarm Manager used to schedule long set up or handover retries. */
-    private final @NonNull AlarmManager mAlarmManager;
+    @NonNull
+    private final AlarmManager mAlarmManager;
 
     /**
      * The data retry callback. This is only used to notify {@link DataNetworkController} to retry
      * setup data network.
      */
-    private @NonNull Set<DataRetryManagerCallback> mDataRetryManagerCallbacks = new ArraySet<>();
+    @NonNull
+    private final Set<DataRetryManagerCallback> mDataRetryManagerCallbacks = new ArraySet<>();
 
     /** Data service managers. */
-    private @NonNull SparseArray<DataServiceManager> mDataServiceManagers;
+    @NonNull
+    private final SparseArray<DataServiceManager> mDataServiceManagers;
 
     /** Data config manager instance. */
-    private final @NonNull DataConfigManager mDataConfigManager;
+    @NonNull
+    private final DataConfigManager mDataConfigManager;
 
     /** Data profile manager. */
-    private final @NonNull DataProfileManager mDataProfileManager;
+    @NonNull
+    private final DataProfileManager mDataProfileManager;
 
     /** Data setup retry rule list. */
-    private @NonNull List<DataSetupRetryRule> mDataSetupRetryRuleList = new ArrayList<>();
+    @NonNull
+    private List<DataSetupRetryRule> mDataSetupRetryRuleList = new ArrayList<>();
 
     /** Data handover retry rule list. */
-    private @NonNull List<DataHandoverRetryRule> mDataHandoverRetryRuleList = new ArrayList<>();
+    @NonNull
+    private List<DataHandoverRetryRule> mDataHandoverRetryRuleList = new ArrayList<>();
 
     /** Data retry entries. */
-    private final @NonNull List<DataRetryEntry> mDataRetryEntries = new ArrayList<>();
+    @NonNull
+    private final List<DataRetryEntry> mDataRetryEntries = new ArrayList<>();
 
     /**
      * Data throttling entries. Note this only stores throttling requested by networks. We intended
      * not to store frameworks-initiated throttling because they are not explicit/strong throttling
      * requests.
      */
-    private final @NonNull List<DataThrottlingEntry> mDataThrottlingEntries = new ArrayList<>();
+    @NonNull
+    private final List<DataThrottlingEntry> mDataThrottlingEntries = new ArrayList<>();
 
     /**
      * Represent a single data setup/handover throttling reported by networks.
@@ -205,31 +219,37 @@
         /**
          * The data profile that is being throttled for setup/handover retry.
          */
-        public final @NonNull DataProfile dataProfile;
+        @NonNull
+        public final DataProfile dataProfile;
 
         /**
          * The associated network request list when throttling happened. Should be {@code null} when
          * retry type is {@link ThrottleStatus#RETRY_TYPE_HANDOVER}.
          */
-        public final @Nullable NetworkRequestList networkRequestList;
+        @Nullable
+        public final NetworkRequestList networkRequestList;
 
         /**
-         * @param dataNetwork The data network that is being throttled for handover retry. Should be
+         * The data network that is being throttled for handover retry. Should be
          * {@code null} when retryType is {@link ThrottleStatus#RETRY_TYPE_NEW_CONNECTION}.
          */
-        public final @Nullable DataNetwork dataNetwork;
+        @Nullable
+        public final DataNetwork dataNetwork;
 
         /** The transport that the data profile has been throttled on. */
-        public final @TransportType int transport;
+        @TransportType
+        public final int transport;
 
         /** The retry type when throttling expires. */
-        public final @RetryType int retryType;
+        @RetryType
+        public final int retryType;
 
         /**
          * The expiration time of data throttling. This is the time retrieved from
          * {@link SystemClock#elapsedRealtime()}.
          */
-        public final @ElapsedRealtimeLong long expirationTimeMillis;
+        @ElapsedRealtimeLong
+        public final long expirationTimeMillis;
 
         /**
          * Constructor.
@@ -257,7 +277,8 @@
         }
 
         @Override
-        public @NonNull String toString() {
+        @NonNull
+        public String toString() {
             return "[DataThrottlingEntry: dataProfile=" + dataProfile + ", request list="
                     + networkRequestList + ", dataNetwork=" + dataNetwork + ", transport="
                     + AccessNetworkConstants.transportTypeToString(transport) + ", expiration time="
@@ -293,13 +314,17 @@
          * capabilities specified here, then retry will happen. Empty set indicates the retry rule
          * is not using network capabilities.
          */
-        protected @NonNull @NetCapability Set<Integer> mNetworkCapabilities = new ArraySet<>();
+        @NonNull
+        @NetCapability
+        protected Set<Integer> mNetworkCapabilities = new ArraySet<>();
 
         /**
          * The fail causes. If data setup failed with certain fail causes, then retry will happen.
          * Empty set indicates the retry rule is not using the fail causes.
          */
-        protected @NonNull @DataFailureCause Set<Integer> mFailCauses = new ArraySet<>();
+        @NonNull
+        @DataFailureCause
+        protected Set<Integer> mFailCauses = new ArraySet<>();
 
         public DataRetryRule(@NonNull String ruleString) {
             if (TextUtils.isEmpty(ruleString)) {
@@ -353,7 +378,8 @@
          * @return The data network setup retry intervals in milliseconds. If this is empty, then
          * {@link #getMaxRetries()} must return 0.
          */
-        public @NonNull List<Long> getRetryIntervalsMillis() {
+        @NonNull
+        public List<Long> getRetryIntervalsMillis() {
             return mRetryIntervalsMillis;
         }
 
@@ -372,43 +398,44 @@
          * happen. Empty set indicates the retry rule is not using the fail causes.
          */
         @VisibleForTesting
-        public @NonNull @DataFailureCause Set<Integer> getFailCauses() {
+        @NonNull
+        @DataFailureCause
+        public Set<Integer> getFailCauses() {
             return mFailCauses;
         }
     }
 
     /**
      * Represent a rule for data setup retry.
-     *
+     * <p>
      * The syntax of the retry rule:
      * 1. Retry based on {@link NetworkCapabilities}. Note that only APN-type network capabilities
      *    are supported. If the capabilities are not specified, then the retry rule only applies
      *    to the current failed APN used in setup data call request.
      * "capabilities=[netCaps1|netCaps2|...], [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
-     *
+     * <p>
      * 2. Retry based on {@link DataFailCause}
      * "fail_causes=[cause1|cause2|cause3|..], [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
-     *
+     * <p>
      * 3. Retry based on {@link NetworkCapabilities} and {@link DataFailCause}. Note that only
      *    APN-type network capabilities are supported.
      * "capabilities=[netCaps1|netCaps2|...], fail_causes=[cause1|cause2|cause3|...],
      *     [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
-     *
+     * <p>
      * 4. Permanent fail causes (no timer-based retry) on the current failed APN. Retry interval
      *    is specified for retrying the next available APN.
      * "permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|65543|65547|
      *     2252|2253|2254, retry_interval=2500"
-     *
+     * <p>
      * For example,
      * "capabilities=eims, retry_interval=1000, maximum_retries=20" means if the attached
      * network request is emergency, then retry data network setup every 1 second for up to 20
      * times.
-     *
+     * <p>
      * "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
      * "5000|10000|15000|20000|40000|60000|120000|240000|600000|1200000|1800000"
      * "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s,
      * 10s, 15s, 20s, 40s, 1m, 2m, 4m, 10m, 20m, 30m, 30m, 30m, until reaching 20 retries.
-     *
      */
     public static class DataSetupRetryRule extends DataRetryRule {
         private static final String RULE_TAG_PERMANENT_FAIL_CAUSES = "permanent_fail_causes";
@@ -469,7 +496,9 @@
          * capabilities.
          */
         @VisibleForTesting
-        public @NonNull @NetCapability Set<Integer> getNetworkCapabilities() {
+        @NonNull
+        @NetCapability
+        public Set<Integer> getNetworkCapabilities() {
             return mNetworkCapabilities;
         }
 
@@ -487,7 +516,7 @@
          * @param cause Fail cause from previous setup data request.
          * @return {@code true} if the retry rule can be matched.
          */
-        public boolean canBeMatched(@NonNull @NetCapability int networkCapability,
+        public boolean canBeMatched(@NetCapability int networkCapability,
                 @DataFailureCause int cause) {
             if (!mFailCauses.isEmpty() && !mFailCauses.contains(cause)) {
                 return false;
@@ -510,22 +539,22 @@
 
     /**
      * Represent a handover data network retry rule.
-     *
+     * <p>
      * The syntax of the retry rule:
      * 1. Retry when handover fails.
      * "retry_interval=[n1|n2|n3|...], [maximum_retries=n]"
-     *
+     * <p>
      * For example,
      * "retry_interval=1000|3000|5000, maximum_retries=10" means handover retry will happen in 1s,
      * 3s, 5s, 5s, 5s....up to 10 times.
-     *
+     * <p>
      * 2. Retry when handover fails with certain fail causes.
      * "retry_interval=[n1|n2|n3|...], fail_causes=[cause1|cause2|cause3|...], [maximum_retries=n]
-     *
+     * <p>
      * For example,
      * "retry_interval=1000, maximum_retries=3, fail_causes=5" means handover retry every 1 second
      * for up to 3 times when handover fails with the cause 5.
-     *
+     * <p>
      * "maximum_retries=0, fail_causes=6|10|67" means handover retry should not happen for those
      * causes.
      */
@@ -573,7 +602,8 @@
         public @interface DataRetryState {}
 
         /** The rule used for this data retry. {@code null} if the retry is requested by network. */
-        public final @Nullable DataRetryRule appliedDataRetryRule;
+        @Nullable
+        public final DataRetryRule appliedDataRetryRule;
 
         /** The retry delay in milliseconds. */
         public final long retryDelayMillis;
@@ -582,13 +612,15 @@
          * Retry elapsed time. This is the system elapsed time retrieved from
          * {@link SystemClock#elapsedRealtime()}.
          */
-        public final @ElapsedRealtimeLong long retryElapsedTime;
+        @ElapsedRealtimeLong
+        public final long retryElapsedTime;
 
         /** The retry state. */
         protected int mRetryState = RETRY_STATE_NOT_RETRIED;
 
         /** Timestamp when a state is set. For debugging purposes only. */
-        protected @ElapsedRealtimeLong long mRetryStateTimestamp = 0;
+        @ElapsedRealtimeLong
+        protected long mRetryStateTimestamp;
 
         /**
          * Constructor
@@ -617,7 +649,8 @@
         /**
          * @return Get the retry state.
          */
-        public @DataRetryState int getState() {
+        @DataRetryState
+        public int getState() {
             return mRetryState;
         }
 
@@ -628,13 +661,13 @@
          * @return Retry state in string format.
          */
         public static String retryStateToString(@DataRetryState int retryState) {
-            switch (retryState) {
-                case RETRY_STATE_NOT_RETRIED: return "NOT_RETRIED";
-                case RETRY_STATE_FAILED: return "FAILED";
-                case RETRY_STATE_SUCCEEDED: return "SUCCEEDED";
-                case RETRY_STATE_CANCELLED: return "CANCELLED";
-                default: return "Unknown(" + retryState + ")";
-            }
+            return switch (retryState) {
+                case RETRY_STATE_NOT_RETRIED -> "NOT_RETRIED";
+                case RETRY_STATE_FAILED -> "FAILED";
+                case RETRY_STATE_SUCCEEDED -> "SUCCEEDED";
+                case RETRY_STATE_CANCELLED -> "CANCELLED";
+                default -> "Unknown(" + retryState + ")";
+            };
         }
 
         /**
@@ -649,7 +682,8 @@
             protected long mRetryDelayMillis = TimeUnit.SECONDS.toMillis(5);
 
             /** The applied data retry rule. */
-            protected @Nullable DataRetryRule mAppliedDataRetryRule;
+            @Nullable
+            protected DataRetryRule mAppliedDataRetryRule;
 
             /**
              * Set the data retry delay.
@@ -657,7 +691,8 @@
              * @param retryDelayMillis The retry delay in milliseconds.
              * @return This builder.
              */
-            public @NonNull T setRetryDelay(long retryDelayMillis) {
+            @NonNull
+            public T setRetryDelay(long retryDelayMillis) {
                 mRetryDelayMillis = retryDelayMillis;
                 return (T) this;
             }
@@ -668,7 +703,8 @@
              * @param dataRetryRule The rule that used for this data retry.
              * @return This builder.
              */
-            public @NonNull T setAppliedRetryRule(@NonNull DataRetryRule dataRetryRule) {
+            @NonNull
+            public T setAppliedRetryRule(@NonNull DataRetryRule dataRetryRule) {
                 mAppliedDataRetryRule = dataRetryRule;
                 return (T) this;
             }
@@ -703,16 +739,20 @@
         public @interface SetupRetryType {}
 
         /** Setup retry type. Could be retry by same data profile or same capability. */
-        public final @SetupRetryType int setupRetryType;
+        @SetupRetryType
+        public final int setupRetryType;
 
         /** The network requests to satisfy when retry happens. */
-        public final @NonNull NetworkRequestList networkRequestList;
+        @NonNull
+        public final NetworkRequestList networkRequestList;
 
         /** The data profile that will be used for retry. */
-        public final @Nullable DataProfile dataProfile;
+        @Nullable
+        public final DataProfile dataProfile;
 
         /** The transport to retry data setup. */
-        public final @TransportType int transport;
+        @TransportType
+        public final int transport;
 
         /**
          * Constructor
@@ -743,11 +783,12 @@
          * @return Retry type in string format.
          */
         private static String retryTypeToString(@SetupRetryType int setupRetryType) {
-            switch (setupRetryType) {
-                case RETRY_TYPE_DATA_PROFILE: return "BY_PROFILE";
-                case RETRY_TYPE_NETWORK_REQUESTS: return "BY_NETWORK_REQUESTS";
-                default: return "Unknown(" + setupRetryType + ")";
-            }
+            return switch (setupRetryType) {
+                case RETRY_TYPE_DATA_PROFILE -> "BY_PROFILE";
+                case RETRY_TYPE_NETWORK_REQUESTS -> "BY_NETWORK_REQUESTS";
+                case RETRY_TYPE_UNKNOWN -> "UNKNOWN";
+                default -> "Unknown(" + setupRetryType + ")";
+            };
         }
 
         @Override
@@ -768,16 +809,20 @@
          */
         public static class Builder<T extends Builder<T>> extends DataRetryEntry.Builder<T> {
             /** Data setup retry type. Could be retry by same data profile or same capabilities. */
-            private @SetupRetryType int mSetupRetryType = RETRY_TYPE_UNKNOWN;
+            @SetupRetryType
+            private int mSetupRetryType = RETRY_TYPE_UNKNOWN;
 
             /** The network requests to satisfy when retry happens. */
-            private @NonNull NetworkRequestList mNetworkRequestList;
+            @NonNull
+            private NetworkRequestList mNetworkRequestList;
 
             /** The data profile that will be used for retry. */
-            private @Nullable DataProfile mDataProfile;
+            @Nullable
+            private DataProfile mDataProfile;
 
             /** The transport to retry data setup. */
-            private @TransportType int mTransport = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
+            @TransportType
+            private int mTransport = AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
 
             /**
              * Set the data retry type.
@@ -786,7 +831,8 @@
              * capabilities.
              * @return This builder.
              */
-            public @NonNull Builder<T> setSetupRetryType(@SetupRetryType int setupRetryType) {
+            @NonNull
+            public Builder<T> setSetupRetryType(@SetupRetryType int setupRetryType) {
                 mSetupRetryType = setupRetryType;
                 return this;
             }
@@ -797,7 +843,8 @@
              * @param networkRequestList The network requests to satisfy when retry happens.
              * @return This builder.
              */
-            public @NonNull Builder<T> setNetworkRequestList(
+            @NonNull
+            public Builder<T> setNetworkRequestList(
                     @NonNull NetworkRequestList networkRequestList) {
                 mNetworkRequestList = networkRequestList;
                 return this;
@@ -809,7 +856,8 @@
              * @param dataProfile The data profile that will be used for retry.
              * @return This builder.
              */
-            public @NonNull Builder<T> setDataProfile(@NonNull DataProfile dataProfile) {
+            @NonNull
+            public Builder<T> setDataProfile(@NonNull DataProfile dataProfile) {
                 mDataProfile = dataProfile;
                 return this;
             }
@@ -820,7 +868,8 @@
              * @param transport The transport to retry data setup.
              * @return This builder.
              */
-            public @NonNull Builder<T> setTransport(@TransportType int transport) {
+            @NonNull
+            public Builder<T> setTransport(@TransportType int transport) {
                 mTransport = transport;
                 return this;
             }
@@ -830,7 +879,8 @@
              *
              * @return The instance of {@link DataSetupRetryEntry}.
              */
-            public @NonNull DataSetupRetryEntry build() {
+            @NonNull
+            public DataSetupRetryEntry build() {
                 if (mNetworkRequestList == null) {
                     throw new IllegalArgumentException("network request list is not specified.");
                 }
@@ -854,7 +904,8 @@
      */
     public static class DataHandoverRetryEntry extends DataRetryEntry {
         /** The data network to be retried for handover. */
-        public final @NonNull DataNetwork dataNetwork;
+        @NonNull
+        public final DataNetwork dataNetwork;
 
         /**
          * Constructor.
@@ -886,7 +937,8 @@
          */
         public static class Builder<T extends Builder<T>> extends DataRetryEntry.Builder<T> {
             /** The data network to be retried for handover. */
-            public @NonNull DataNetwork mDataNetwork;
+            @NonNull
+            public DataNetwork mDataNetwork;
 
             /**
              * Set the data retry type.
@@ -895,7 +947,8 @@
              *
              * @return This builder.
              */
-            public @NonNull Builder<T> setDataNetwork(@NonNull DataNetwork dataNetwork) {
+            @NonNull
+            public Builder<T> setDataNetwork(@NonNull DataNetwork dataNetwork) {
                 mDataNetwork = dataNetwork;
                 return this;
             }
@@ -905,7 +958,8 @@
              *
              * @return The instance of {@link DataHandoverRetryEntry}.
              */
-            public @NonNull DataHandoverRetryEntry build() {
+            @NonNull
+            public DataHandoverRetryEntry build() {
                 return new DataHandoverRetryEntry(mDataNetwork,
                         (DataHandoverRetryRule) mAppliedDataRetryRule, mRetryDelayMillis);
             }
@@ -1168,7 +1222,7 @@
             // when unthrottling happens, we still want to retry and we'll need
             // a type there so we know what to retry. Using RETRY_TYPE_NONE
             // ThrottleStatus is just for API backwards compatibility reason.
-            updateThrottleStatus(dataProfile, requestList, null,
+            throttleDataProfile(dataProfile, requestList, null,
                     ThrottleStatus.RETRY_TYPE_NEW_CONNECTION, transport, Long.MAX_VALUE);
             return;
         } else if (retryDelayMillis != DataCallResponse.RETRY_DURATION_UNDEFINED) {
@@ -1180,7 +1234,7 @@
                     .setDataProfile(dataProfile)
                     .setTransport(transport)
                     .build();
-            updateThrottleStatus(dataProfile, requestList, null,
+            throttleDataProfile(dataProfile, requestList, null,
                     ThrottleStatus.RETRY_TYPE_NEW_CONNECTION, transport,
                     dataSetupRetryEntry.retryElapsedTime);
             schedule(dataSetupRetryEntry);
@@ -1192,7 +1246,7 @@
 
         boolean retryScheduled = false;
         List<NetworkRequestList> groupedNetworkRequestLists =
-                DataUtils.getGroupedNetworkRequestList(requestList);
+                DataUtils.getGroupedNetworkRequestList(requestList, mFlags);
         for (DataSetupRetryRule retryRule : mDataSetupRetryRuleList) {
             if (retryRule.isPermanentFailCauseRule() && retryRule.getFailCauses().contains(cause)) {
                 if (dataProfile.getApnSetting() != null) {
@@ -1298,7 +1352,7 @@
             // when unthrottling happens, we still want to retry and we'll need
             // a type there so we know what to retry. Using RETRY_TYPE_NONE
             // ThrottleStatus is just for API backwards compatibility reason.
-            updateThrottleStatus(dataNetwork.getDataProfile(),
+            throttleDataProfile(dataNetwork.getDataProfile(),
                     dataNetwork.getAttachedNetworkRequestList(), dataNetwork,
                     ThrottleStatus.RETRY_TYPE_HANDOVER, targetTransport, Long.MAX_VALUE);
         } else if (retryDelayMillis != DataCallResponse.RETRY_DURATION_UNDEFINED) {
@@ -1308,7 +1362,7 @@
                     .setDataNetwork(dataNetwork)
                     .build();
 
-            updateThrottleStatus(dataNetwork.getDataProfile(),
+            throttleDataProfile(dataNetwork.getDataProfile(),
                     dataNetwork.getAttachedNetworkRequestList(), dataNetwork,
                     ThrottleStatus.RETRY_TYPE_HANDOVER, targetTransport,
                     dataHandoverRetryEntry.retryElapsedTime);
@@ -1534,7 +1588,7 @@
      * @param expirationTime The expiration time of data throttling. This is the time retrieved from
      * {@link SystemClock#elapsedRealtime()}.
      */
-    private void updateThrottleStatus(@NonNull DataProfile dataProfile,
+    private void throttleDataProfile(@NonNull DataProfile dataProfile,
             @Nullable NetworkRequestList networkRequestList,
             @Nullable DataNetwork dataNetwork, @RetryType int retryType,
             @TransportType int transport, @ElapsedRealtimeLong long expirationTime) {
@@ -1565,21 +1619,7 @@
                 ? ThrottleStatus.RETRY_TYPE_NONE : retryType;
 
         // Report to the clients.
-        final List<ThrottleStatus> throttleStatusList = new ArrayList<>();
-        if (dataProfile.getApnSetting() != null) {
-            throttleStatusList.addAll(dataProfile.getApnSetting().getApnTypes().stream()
-                    .map(apnType -> new ThrottleStatus.Builder()
-                            .setApnType(apnType)
-                            .setRetryType(dataRetryType)
-                            .setSlotIndex(mPhone.getPhoneId())
-                            .setThrottleExpiryTimeMillis(expirationTime)
-                            .setTransportType(transport)
-                            .build())
-                    .collect(Collectors.toList()));
-        }
-
-        mDataRetryManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
-                () -> callback.onThrottleStatusChanged(throttleStatusList)));
+        notifyThrottleStatus(dataProfile, expirationTime, dataRetryType, transport);
     }
 
     /**
@@ -1650,23 +1690,9 @@
         // Make it final so it can be used in the lambda function below.
         final int dataRetryType = retryType;
 
-        if (unthrottledProfile != null && unthrottledProfile.getApnSetting() != null) {
-            unthrottledProfile.getApnSetting().setPermanentFailed(false);
-            throttleStatusList.addAll(unthrottledProfile.getApnSetting().getApnTypes().stream()
-                    .map(apnType -> new ThrottleStatus.Builder()
-                            .setApnType(apnType)
-                            .setSlotIndex(mPhone.getPhoneId())
-                            .setNoThrottle()
-                            .setRetryType(dataRetryType)
-                            .setTransportType(transport)
-                            .build())
-                    .collect(Collectors.toList()));
-        }
-
-        mDataRetryManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
-                () -> callback.onThrottleStatusChanged(throttleStatusList)));
-
         if (unthrottledProfile != null) {
+            notifyThrottleStatus(unthrottledProfile, ThrottleStatus.Builder.NO_THROTTLE_EXPIRY_TIME,
+                    dataRetryType, transport);
             // cancel pending retries since we will soon schedule an immediate retry
             cancelRetriesForDataProfile(unthrottledProfile, transport);
         }
@@ -1799,6 +1825,60 @@
                         && ((DataHandoverRetryEntry) entry).dataNetwork == dataNetwork
                         && entry.getState() == DataRetryEntry.RETRY_STATE_NOT_RETRIED)
                 .forEach(entry -> entry.setState(DataRetryEntry.RETRY_STATE_CANCELLED));
+
+        long now = SystemClock.elapsedRealtime();
+        DataThrottlingEntry dataUnThrottlingEntry = mDataThrottlingEntries.stream()
+                .filter(entry -> dataNetwork == entry.dataNetwork
+                        && entry.expirationTimeMillis > now).findAny().orElse(null);
+        if (dataUnThrottlingEntry == null) {
+            return;
+        }
+        log("onCancelPendingHandoverRetry removed throttling entry:" + dataUnThrottlingEntry);
+        DataProfile unThrottledProfile =
+                dataUnThrottlingEntry.dataNetwork.getDataProfile();
+        final int transport = dataUnThrottlingEntry.transport;
+
+        notifyThrottleStatus(unThrottledProfile, ThrottleStatus.Builder.NO_THROTTLE_EXPIRY_TIME,
+                ThrottleStatus.RETRY_TYPE_HANDOVER, transport);
+        mDataThrottlingEntries.removeIf(entry -> dataNetwork == entry.dataNetwork);
+    }
+
+    /**
+     * Notify listeners of throttle status for a given data profile
+     *
+     * @param dataProfile Data profile for this throttling status notification
+     * @param expirationTime Expiration time of throttling status. {@link
+     * ThrottleStatus.Builder#NO_THROTTLE_EXPIRY_TIME} indicates un-throttling.
+     * @param dataRetryType Retry type of this throttling notification.
+     * @param transportType Transport type of this throttling notification.
+     */
+    private void notifyThrottleStatus(
+            @NonNull DataProfile dataProfile, long expirationTime, @RetryType int dataRetryType,
+            @TransportType int transportType) {
+        if (dataProfile.getApnSetting() != null) {
+            final boolean unThrottled =
+                    expirationTime == ThrottleStatus.Builder.NO_THROTTLE_EXPIRY_TIME;
+            if (unThrottled) {
+                dataProfile.getApnSetting().setPermanentFailed(false);
+            }
+            final List<ThrottleStatus> throttleStatusList = new ArrayList<>(
+                    dataProfile.getApnSetting().getApnTypes().stream()
+                            .map(apnType -> {
+                                ThrottleStatus.Builder builder = new ThrottleStatus.Builder()
+                                        .setApnType(apnType)
+                                        .setSlotIndex(mPhone.getPhoneId())
+                                        .setRetryType(dataRetryType)
+                                        .setTransportType(transportType);
+                                if (unThrottled) {
+                                    builder.setNoThrottle();
+                                } else {
+                                    builder.setThrottleExpiryTimeMillis(expirationTime);
+                                }
+                                return builder.build();
+                            }).toList());
+            mDataRetryManagerCallbacks.forEach(callback -> callback.invokeFromExecutor(
+                    () -> callback.onThrottleStatusChanged(throttleStatusList)));
+        }
     }
 
     /**
@@ -1839,23 +1919,17 @@
      * @param reason The reason
      * @return The reason in string format.
      */
-    private static @NonNull String resetReasonToString(int reason) {
-        switch (reason) {
-            case RESET_REASON_DATA_PROFILES_CHANGED:
-                return "DATA_PROFILES_CHANGED";
-            case RESET_REASON_RADIO_ON:
-                return "RADIO_ON";
-            case RESET_REASON_MODEM_RESTART:
-                return "MODEM_RESTART";
-            case RESET_REASON_DATA_SERVICE_BOUND:
-                return "DATA_SERVICE_BOUND";
-            case RESET_REASON_DATA_CONFIG_CHANGED:
-                return "DATA_CONFIG_CHANGED";
-            case RESET_REASON_TAC_CHANGED:
-                return "TAC_CHANGED";
-            default:
-                return "UNKNOWN(" + reason + ")";
-        }
+    @NonNull
+    private static String resetReasonToString(int reason) {
+        return switch (reason) {
+            case RESET_REASON_DATA_PROFILES_CHANGED -> "DATA_PROFILES_CHANGED";
+            case RESET_REASON_RADIO_ON -> "RADIO_ON";
+            case RESET_REASON_MODEM_RESTART -> "MODEM_RESTART";
+            case RESET_REASON_DATA_SERVICE_BOUND -> "DATA_SERVICE_BOUND";
+            case RESET_REASON_DATA_CONFIG_CHANGED -> "DATA_CONFIG_CHANGED";
+            case RESET_REASON_TAC_CHANGED -> "TAC_CHANGED";
+            default -> "UNKNOWN(" + reason + ")";
+        };
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/data/DataServiceManager.java b/src/java/com/android/internal/telephony/data/DataServiceManager.java
index ee66a6a..a71bd10 100644
--- a/src/java/com/android/internal/telephony/data/DataServiceManager.java
+++ b/src/java/com/android/internal/telephony/data/DataServiceManager.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony.data;
 
-import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 import static android.text.format.DateUtils.SECOND_IN_MILLIS;
 
 import android.annotation.NonNull;
@@ -61,6 +60,7 @@
 import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.telephony.Rlog;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -82,17 +82,12 @@
 
     private static final int EVENT_BIND_DATA_SERVICE = 1;
 
-    private static final int EVENT_WATCHDOG_TIMEOUT = 2;
-
-    private static final long REQUEST_UNRESPONDED_TIMEOUT = 10 * MINUTE_IN_MILLIS; // 10 mins
-
     private static final long CHANGE_PERMISSION_TIMEOUT_MS = 15 * SECOND_IN_MILLIS; // 15 secs
 
     private final Phone mPhone;
 
     private final String mTag;
 
-    private final CarrierConfigManager mCarrierConfigManager;
     private final AppOpsManager mAppOps;
     private final LegacyPermissionManager mPermissionManager;
 
@@ -102,8 +97,6 @@
 
     private IDataService mIDataService;
 
-    private DataServiceManagerDeathRecipient mDeathRecipient;
-
     private final RegistrantList mServiceBindingChangedRegistrants = new RegistrantList();
 
     private final Map<IBinder, Message> mMessageMap = new ConcurrentHashMap<>();
@@ -118,7 +111,7 @@
 
     private String mLastBoundPackageName;
 
-    private List<DataCallResponse> mLastDataCallResponseList = Collections.EMPTY_LIST;
+    private List<DataCallResponse> mLastDataCallResponseList = new ArrayList<>();
 
     private class DataServiceManagerDeathRecipient implements IBinder.DeathRecipient {
         @Override
@@ -137,7 +130,7 @@
             mMessageMap.clear();
 
             // Tear down all connections
-            mLastDataCallResponseList = Collections.EMPTY_LIST;
+            mLastDataCallResponseList = new ArrayList<>();
             mDataCallListChangedRegistrants.notifyRegistrants(
                     new AsyncResult(null, Collections.EMPTY_LIST, null));
         }
@@ -209,13 +202,11 @@
         public void onServiceConnected(ComponentName name, IBinder service) {
             if (DBG) log("onServiceConnected: " + name);
             mIDataService = IDataService.Stub.asInterface(service);
-            mDeathRecipient = new DataServiceManagerDeathRecipient();
             mBound = true;
             mLastBoundPackageName = getDataServicePackageName();
-            removeMessages(EVENT_WATCHDOG_TIMEOUT);
 
             try {
-                service.linkToDeath(mDeathRecipient, 0);
+                service.linkToDeath(new DataServiceManagerDeathRecipient(), 0);
                 mIDataService.createDataServiceProvider(mPhone.getPhoneId());
                 mIDataService.registerForDataCallListChanged(mPhone.getPhoneId(),
                         new DataServiceCallbackWrapper("dataCallListChanged"));
@@ -230,7 +221,6 @@
         @Override
         public void onServiceDisconnected(ComponentName name) {
             if (DBG) log("onServiceDisconnected");
-            removeMessages(EVENT_WATCHDOG_TIMEOUT);
             mIDataService = null;
             mBound = false;
             mServiceBindingChangedRegistrants.notifyResult(false);
@@ -257,7 +247,6 @@
                 log("onSetupDataCallComplete. resultCode = " + resultCode + ", response = "
                         + response);
             }
-            removeMessages(EVENT_WATCHDOG_TIMEOUT, DataServiceCallbackWrapper.this);
             Message msg = mMessageMap.remove(asBinder());
             if (msg != null) {
                 msg.getData().putParcelable(DATA_CALL_RESPONSE, response);
@@ -270,7 +259,6 @@
         @Override
         public void onDeactivateDataCallComplete(@DataServiceCallback.ResultCode int resultCode) {
             if (DBG) log("onDeactivateDataCallComplete. resultCode = " + resultCode);
-            removeMessages(EVENT_WATCHDOG_TIMEOUT, DataServiceCallbackWrapper.this);
             Message msg = mMessageMap.remove(asBinder());
             sendCompleteMessage(msg, resultCode);
         }
@@ -326,7 +314,7 @@
         @Override
         public void onDataCallListChanged(List<DataCallResponse> dataCallList) {
             mLastDataCallResponseList =
-                    dataCallList != null ? dataCallList : Collections.EMPTY_LIST;
+                    dataCallList != null ? dataCallList : new ArrayList<>();
             mDataCallListChangedRegistrants.notifyRegistrants(
                     new AsyncResult(null, dataCallList, null));
         }
@@ -334,7 +322,6 @@
         @Override
         public void onHandoverStarted(@DataServiceCallback.ResultCode int resultCode) {
             if (DBG) log("onHandoverStarted. resultCode = " + resultCode);
-            removeMessages(EVENT_WATCHDOG_TIMEOUT, DataServiceCallbackWrapper.this);
             Message msg = mMessageMap.remove(asBinder());
             sendCompleteMessage(msg, resultCode);
         }
@@ -342,7 +329,6 @@
         @Override
         public void onHandoverCancelled(@DataServiceCallback.ResultCode int resultCode) {
             if (DBG) log("onHandoverCancelled. resultCode = " + resultCode);
-            removeMessages(EVENT_WATCHDOG_TIMEOUT, DataServiceCallbackWrapper.this);
             Message msg = mMessageMap.remove(asBinder());
             sendCompleteMessage(msg, resultCode);
         }
@@ -383,8 +369,8 @@
         mTag = "DSM-" + (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN ? "C-"
                 : "I-") + mPhone.getPhoneId();
         mBound = false;
-        mCarrierConfigManager = (CarrierConfigManager) phone.getContext().getSystemService(
-                Context.CARRIER_CONFIG_SERVICE);
+        CarrierConfigManager carrierConfigManager = phone.getContext().getSystemService(
+                CarrierConfigManager.class);
         // NOTE: Do NOT use AppGlobals to retrieve the permission manager; AppGlobals
         // caches the service instance, but we need to explicitly request a new service
         // so it can be mocked out for tests
@@ -393,17 +379,18 @@
         mAppOps = (AppOpsManager) phone.getContext().getSystemService(Context.APP_OPS_SERVICE);
 
         // Callback is executed in handler thread to directly handle config change.
-        mCarrierConfigManager.registerCarrierConfigChangeListener(this::post,
-                (slotIndex, subId, carrierId, specificCarrierId) -> {
-                    if (slotIndex == mPhone.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.
-                        if (DBG) log("Carrier config changed. Try to bind data service.");
-                        rebindDataService();
-                    }
-                });
-
+        if (carrierConfigManager != null) {
+            carrierConfigManager.registerCarrierConfigChangeListener(this::post,
+                    (slotIndex, subId, carrierId, specificCarrierId) -> {
+                        if (slotIndex == mPhone.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.
+                            if (DBG) log("Carrier config changed. Try to bind data service.");
+                            rebindDataService();
+                        }
+                    });
+        }
         PhoneConfigurationManager.registerForMultiSimConfigChange(
                 this, EVENT_BIND_DATA_SERVICE, null);
 
@@ -417,29 +404,13 @@
      */
     @Override
     public void handleMessage(Message msg) {
-        switch (msg.what) {
-            case EVENT_BIND_DATA_SERVICE:
-                rebindDataService();
-                break;
-            case EVENT_WATCHDOG_TIMEOUT:
-                handleRequestUnresponded((DataServiceCallbackWrapper) msg.obj);
-                break;
-            default:
-                loge("Unhandled event " + msg.what);
+        if (msg.what == EVENT_BIND_DATA_SERVICE) {
+            rebindDataService();
+        } else {
+            loge("Unhandled event " + msg.what);
         }
     }
 
-    private void handleRequestUnresponded(DataServiceCallbackWrapper callback) {
-        String message = "Request " + callback.getTag() + " unresponded on transport "
-                + AccessNetworkConstants.transportTypeToString(mTransportType) + " in "
-                + REQUEST_UNRESPONDED_TIMEOUT / 1000 + " seconds.";
-        log(message);
-        // Using fixed UUID to avoid duplicate bugreport notification
-        AnomalyReporter.reportAnomaly(
-                UUID.fromString("f5d5cbe6-9bd6-4009-b764-42b1b649b1de"),
-                message, mPhone.getCarrierId());
-    }
-
     private void unbindDataService() {
         // Start by cleaning up all packages that *shouldn't* have permissions.
         revokePermissionsFromUnusedDataServices();
@@ -473,7 +444,7 @@
             return;
         }
 
-        Intent intent = null;
+        Intent intent;
         String className = getDataServiceClassName();
         if (TextUtils.isEmpty(className)) {
             intent = new Intent(DataService.SERVICE_INTERFACE);
@@ -512,7 +483,8 @@
         bindDataService(packageName);
     }
 
-    private @NonNull Set<String> getAllDataServicePackageNames() {
+    @NonNull
+    private Set<String> getAllDataServicePackageNames() {
         // Cowardly using the public PackageManager interface here.
         // Note: This matches only packages that were installed on the system image. If we ever
         // expand the permissions model to allow CarrierPrivileged packages, then this will need
@@ -540,7 +512,7 @@
 
     /**
      * Get the data service package by transport type.
-     *
+     * <p>
      * When we bind to a DataService package, we need to revoke permissions from stale
      * packages; we need to exclude data packages for all transport types, so we need to
      * to be able to query by transport type.
@@ -552,21 +524,19 @@
         String packageName;
         int resourceId;
         String carrierConfig;
-
         switch (transportType) {
-            case AccessNetworkConstants.TRANSPORT_TYPE_WWAN:
+            case AccessNetworkConstants.TRANSPORT_TYPE_WWAN -> {
                 resourceId = com.android.internal.R.string.config_wwan_data_service_package;
                 carrierConfig = CarrierConfigManager
                         .KEY_CARRIER_DATA_SERVICE_WWAN_PACKAGE_OVERRIDE_STRING;
-                break;
-            case AccessNetworkConstants.TRANSPORT_TYPE_WLAN:
+            }
+            case AccessNetworkConstants.TRANSPORT_TYPE_WLAN -> {
                 resourceId = com.android.internal.R.string.config_wlan_data_service_package;
                 carrierConfig = CarrierConfigManager
                         .KEY_CARRIER_DATA_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING;
-                break;
-            default:
-                throw new IllegalStateException("Transport type not WWAN or WLAN. type="
-                        + AccessNetworkConstants.transportTypeToString(mTransportType));
+            }
+            default -> throw new IllegalStateException("Transport type not WWAN or WLAN. type="
+                    + AccessNetworkConstants.transportTypeToString(mTransportType));
         }
 
         // Read package name from resource overlay
@@ -604,19 +574,18 @@
         int resourceId;
         String carrierConfig;
         switch (transportType) {
-            case AccessNetworkConstants.TRANSPORT_TYPE_WWAN:
+            case AccessNetworkConstants.TRANSPORT_TYPE_WWAN -> {
                 resourceId = com.android.internal.R.string.config_wwan_data_service_class;
                 carrierConfig = CarrierConfigManager
                         .KEY_CARRIER_DATA_SERVICE_WWAN_CLASS_OVERRIDE_STRING;
-                break;
-            case AccessNetworkConstants.TRANSPORT_TYPE_WLAN:
+            }
+            case AccessNetworkConstants.TRANSPORT_TYPE_WLAN -> {
                 resourceId = com.android.internal.R.string.config_wlan_data_service_class;
                 carrierConfig = CarrierConfigManager
                         .KEY_CARRIER_DATA_SERVICE_WLAN_CLASS_OVERRIDE_STRING;
-                break;
-            default:
-                throw new IllegalStateException("Transport type not WWAN or WLAN. type="
-                        + transportType);
+            }
+            default -> throw new IllegalStateException("Transport type not WWAN or WLAN. type="
+                    + transportType);
         }
 
         // Read package name from resource overlay
@@ -680,8 +649,6 @@
             mMessageMap.put(callback.asBinder(), onCompleteMessage);
         }
         try {
-            sendMessageDelayed(obtainMessage(EVENT_WATCHDOG_TIMEOUT, callback),
-                    REQUEST_UNRESPONDED_TIMEOUT);
             mIDataService.setupDataCall(mPhone.getPhoneId(), accessNetworkType, dataProfile,
                     isRoaming, allowRoaming, reason, linkProperties, pduSessionId, sliceInfo,
                     trafficDescriptor, matchAllRuleAllowed, callback);
@@ -720,8 +687,6 @@
             mMessageMap.put(callback.asBinder(), onCompleteMessage);
         }
         try {
-            sendMessageDelayed(obtainMessage(EVENT_WATCHDOG_TIMEOUT, callback),
-                    REQUEST_UNRESPONDED_TIMEOUT);
             mIDataService.deactivateDataCall(mPhone.getPhoneId(), cid, reason, callback);
         } catch (RemoteException e) {
             loge("Cannot invoke deactivateDataCall on data service.");
@@ -732,13 +697,13 @@
 
     /**
      * Indicates that a handover has begun.  This is called on the source transport.
-     *
+     * <p>
      * Any resources being transferred cannot be released while a
      * handover is underway.
-     *
+     * <p>
      * If a handover was unsuccessful, then the framework calls DataServiceManager#cancelHandover.
      * The target transport retains ownership over any of the resources being transferred.
-     *
+     * <p>
      * If a handover was successful, the framework calls DataServiceManager#deactivateDataCall with
      * reason HANDOVER. The target transport now owns the transferred resources and is
      * responsible for releasing them.
@@ -756,13 +721,9 @@
 
         DataServiceCallbackWrapper callback =
                 new DataServiceCallbackWrapper("startHandover");
-        if (onCompleteMessage != null) {
-            mMessageMap.put(callback.asBinder(), onCompleteMessage);
-        }
+        mMessageMap.put(callback.asBinder(), onCompleteMessage);
 
         try {
-            sendMessageDelayed(obtainMessage(EVENT_WATCHDOG_TIMEOUT, callback),
-                    REQUEST_UNRESPONDED_TIMEOUT);
             mIDataService.startHandover(mPhone.getPhoneId(), cid, callback);
         } catch (RemoteException e) {
             loge("Cannot invoke startHandover on data service.");
@@ -774,7 +735,7 @@
     /**
      * Indicates that a handover was cancelled after a call to DataServiceManager#startHandover.
      * This is called on the source transport.
-     *
+     * <p>
      * Since the handover was unsuccessful, the source transport retains ownership over any of
      * the resources being transferred and is still responsible for releasing them.
      *
@@ -791,13 +752,9 @@
 
         DataServiceCallbackWrapper callback =
                 new DataServiceCallbackWrapper("cancelHandover");
-        if (onCompleteMessage != null) {
-            mMessageMap.put(callback.asBinder(), onCompleteMessage);
-        }
+        mMessageMap.put(callback.asBinder(), onCompleteMessage);
 
         try {
-            sendMessageDelayed(obtainMessage(EVENT_WATCHDOG_TIMEOUT, callback),
-                    REQUEST_UNRESPONDED_TIMEOUT);
             mIDataService.cancelHandover(mPhone.getPhoneId(), cid, callback);
         } catch (RemoteException e) {
             loge("Cannot invoke cancelHandover on data service.");
@@ -894,9 +851,7 @@
             mIDataService.requestDataCallList(mPhone.getPhoneId(), callback);
         } catch (RemoteException e) {
             loge("Cannot invoke requestDataCallList on data service.");
-            if (callback != null) {
-                mMessageMap.remove(callback.asBinder());
-            }
+            mMessageMap.remove(callback.asBinder());
             sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
         }
     }
@@ -937,17 +892,6 @@
     }
 
     /**
-     * Unregister for apn unthrottled event
-     *
-     * @param h The handler
-     */
-    public void unregisterForApnUnthrottled(Handler h) {
-        if (h != null) {
-            mApnUnthrottledRegistrants.remove(h);
-        }
-    }
-
-    /**
      * Request data network validation.
      *
      * <p>Validates a given data network to ensure that the network can work properly.
@@ -981,9 +925,7 @@
             mIDataService.requestNetworkValidation(mPhone.getPhoneId(), cid, callback);
         } catch (RemoteException e) {
             loge("Cannot invoke requestNetworkValidation on data service.");
-            if (callback != null) {
-                mMessageMap.remove(callback.asBinder());
-            }
+            mMessageMap.remove(callback.asBinder());
             sendCompleteMessage(onCompleteMessage, DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE);
         }
     }
@@ -1005,17 +947,6 @@
         }
     }
 
-    /**
-     * Unregister for data service binding status changed event.
-     *
-     * @param h The handler
-     */
-    public void unregisterForServiceBindingChanged(Handler h) {
-        if (h != null) {
-            mServiceBindingChangedRegistrants.remove(h);
-        }
-    }
-
     private void log(String s) {
         Rlog.d(mTag, s);
     }
diff --git a/src/java/com/android/internal/telephony/data/DataSettingsManager.java b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
index 51e5b7d..fb112d9 100644
--- a/src/java/com/android/internal/telephony/data/DataSettingsManager.java
+++ b/src/java/com/android/internal/telephony/data/DataSettingsManager.java
@@ -47,6 +47,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.SettingsObserver;
+import com.android.internal.telephony.TelephonyCapabilities;
 import com.android.internal.telephony.data.DataConfigManager.DataConfigManagerCallback;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.DeviceTelephonyPropertiesStats;
@@ -91,7 +92,8 @@
     private static final int EVENT_INITIALIZE = 11;
 
     private final Phone mPhone;
-    private final @NonNull FeatureFlags mFeatureFlags;
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
     private final ContentResolver mResolver;
     private final SettingsObserver mSettingsObserver;
     private final String mLogTag;
@@ -100,11 +102,12 @@
     private int mSubId;
 
     /** Data config manager */
-    private final @NonNull DataConfigManager mDataConfigManager;
+    @NonNull
+    private final DataConfigManager mDataConfigManager;
 
     /** Data settings manager callbacks. */
-    private final @NonNull Set<DataSettingsManagerCallback> mDataSettingsManagerCallbacks =
-            new ArraySet<>();
+    @NonNull
+    private final Set<DataSettingsManagerCallback> mDataSettingsManagerCallbacks = new ArraySet<>();
 
     /** Mapping of {@link TelephonyManager.DataEnabledReason} to data enabled values. */
     private final Map<Integer, Boolean> mDataEnabledSettings = new ArrayMap<>();
@@ -271,7 +274,7 @@
     }
 
     private boolean hasCalling() {
-        if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true;
+        if (!TelephonyCapabilities.minimalTelephonyCdmCheck(mFeatureFlags)) return true;
         return mPhone.getContext().getPackageManager().hasSystemFeature(
             PackageManager.FEATURE_TELEPHONY_CALLING);
     }
@@ -718,8 +721,9 @@
      * @param policies New mobile data policies in String format.
      * @return A Set of parsed mobile data policies.
      */
-    public @NonNull @MobileDataPolicy Set<Integer> getMobileDataPolicyEnabled(
-            @NonNull String policies) {
+    @NonNull
+    @MobileDataPolicy
+    public Set<Integer> getMobileDataPolicyEnabled(@NonNull String policies) {
         Set<Integer> mobileDataPolicies = new HashSet<>();
         String[] rulesString = policies.trim().split("\\s*,\\s*");
         for (String rule : rulesString) {
@@ -741,7 +745,8 @@
      * @return Parsed mobile data policy. {@link #INVALID_MOBILE_DATA_POLICY} if string can't be
      * parsed into a mobile data policy.
      */
-    private @MobileDataPolicy int parsePolicyFrom(@NonNull String policy) {
+    @MobileDataPolicy
+    private int parsePolicyFrom(@NonNull String policy) {
         int dataPolicy;
         try {
             // parse as new override policy
@@ -810,20 +815,14 @@
 
     private static String dataEnabledChangedReasonToString(
             @TelephonyManager.DataEnabledChangedReason int reason) {
-        switch (reason) {
-            case TelephonyManager.DATA_ENABLED_REASON_USER:
-                return "USER";
-            case TelephonyManager.DATA_ENABLED_REASON_POLICY:
-                return "POLICY";
-            case TelephonyManager.DATA_ENABLED_REASON_CARRIER:
-                return "CARRIER";
-            case TelephonyManager.DATA_ENABLED_REASON_THERMAL:
-                return "THERMAL";
-            case TelephonyManager.DATA_ENABLED_REASON_OVERRIDE:
-                return "OVERRIDE";
-            default:
-                return "UNKNOWN";
-        }
+        return switch (reason) {
+            case TelephonyManager.DATA_ENABLED_REASON_USER -> "USER";
+            case TelephonyManager.DATA_ENABLED_REASON_POLICY -> "POLICY";
+            case TelephonyManager.DATA_ENABLED_REASON_CARRIER -> "CARRIER";
+            case TelephonyManager.DATA_ENABLED_REASON_THERMAL -> "THERMAL";
+            case TelephonyManager.DATA_ENABLED_REASON_OVERRIDE -> "OVERRIDE";
+            default -> "UNKNOWN";
+        };
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
index ee8890a..b9b60a0 100644
--- a/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
@@ -73,12 +73,11 @@
             value = {
                 RECOVERY_ACTION_GET_DATA_CALL_LIST,
                 RECOVERY_ACTION_CLEANUP,
-                RECOVERY_ACTION_REREGISTER,
                 RECOVERY_ACTION_RADIO_RESTART,
                 RECOVERY_ACTION_RESET_MODEM
             })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface RecoveryAction {};
+    public @interface RecoveryAction {}
 
     /* DataStallRecoveryManager queries RIL for link properties (IP addresses, DNS server addresses
      * etc) using RIL_REQUEST_GET_DATA_CALL_LIST.  This will help in cases where the data stall
@@ -92,16 +91,6 @@
      */
     public static final int RECOVERY_ACTION_CLEANUP = 1;
 
-    /**
-     * Add the RECOVERY_ACTION_REREGISTER to align the RecoveryActions between
-     * DataStallRecoveryManager and atoms.proto. In Android T, This action will not process because
-     * the boolean array for skip recovery action is default true in carrier config setting.
-     *
-     * @deprecated Do not use.
-     */
-    @java.lang.Deprecated
-    public static final int RECOVERY_ACTION_REREGISTER = 2;
-
     /* DataStallRecoveryManager will request ServiceStateTracker to send RIL_REQUEST_RADIO_POWER
      * to restart radio. It will restart the radio and re-attch to the network.
      */
@@ -121,9 +110,9 @@
                 RECOVERED_REASON_USER
             })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface RecoveredReason {};
+    public @interface RecoveredReason {}
 
-    /** The reason when data stall recovered. */
+    // The reason when data stall recovered.
     /** The data stall not recovered yet. */
     private static final int RECOVERED_REASON_NONE = 0;
     /** The data stall recovered by our DataStallRecoveryManager. */
@@ -151,24 +140,33 @@
     /** Event for duration milliseconds changed. */
     private static final int EVENT_CONTENT_DSRM_DURATION_MILLIS_CHANGED = 5;
 
-    private final @NonNull Phone mPhone;
-    private final @NonNull String mLogTag;
-    private final @NonNull LocalLog mLocalLog = new LocalLog(128);
-    private final @NonNull FeatureFlags mFeatureFlags;
+    @NonNull
+    private final Phone mPhone;
+    @NonNull
+    private final String mLogTag;
+    @NonNull
+    private final LocalLog mLocalLog = new LocalLog(128);
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
 
     /** Data network controller */
-    private final @NonNull DataNetworkController mDataNetworkController;
+    @NonNull
+    private final DataNetworkController mDataNetworkController;
 
     /** Data config manager */
-    private final @NonNull DataConfigManager mDataConfigManager;
+    @NonNull
+    private final DataConfigManager mDataConfigManager;
 
     /** Cellular data service */
-    private final @NonNull DataServiceManager mWwanDataServiceManager;
+    @NonNull
+    private final DataServiceManager mWwanDataServiceManager;
 
     /** The data stall recovery action. */
-    private @RecoveryAction int mRecoveryAction;
+    @RecoveryAction
+    private int mRecoveryAction;
     /** The elapsed real time of last recovery attempted */
-    private @ElapsedRealtimeLong long mTimeLastRecoveryStartMs;
+    @ElapsedRealtimeLong
+    private long mTimeLastRecoveryStartMs;
     /** Whether current network is good or not */
     private boolean mIsValidNetwork;
     /** Whether data stall recovery is triggered or not */
@@ -179,11 +177,14 @@
     private boolean mLastActionReported;
     /** The real time for data stall start. */
     @VisibleForTesting
-    public @ElapsedRealtimeLong long mDataStallStartMs;
+    @ElapsedRealtimeLong
+    public long mDataStallStartMs;
     /** Last data stall recovery action. */
-    private @RecoveryAction int mLastAction;
+    @RecoveryAction
+    private int mLastAction;
     /** Last radio power state. */
-    private @RadioPowerState int mRadioPowerState;
+    @RadioPowerState
+    private int mRadioPowerState;
     /** Whether the NetworkCheckTimer start. */
     private boolean mNetworkCheckTimerStarted = false;
     /** Whether radio state changed during data stall. */
@@ -197,15 +198,18 @@
     /** Whether internet network that require validation is connected. */
     private boolean mIsInternetNetworkConnected;
     /** The durations for current recovery action */
-    private @ElapsedRealtimeLong long mTimeElapsedOfCurrentAction;
+    @ElapsedRealtimeLong
+    private long mTimeElapsedOfCurrentAction;
     /** Tracks the total number of validation duration a data stall */
     private int mValidationCount;
     /** Tracks the number of validation for current action during a data stall */
     private int mActionValidationCount;
     /** The array for the timers between recovery actions. */
-    private @NonNull long[] mDataStallRecoveryDelayMillisArray;
+    @NonNull
+    private long[] mDataStallRecoveryDelayMillisArray;
     /** The boolean array for the flags. They are used to skip the recovery actions if needed. */
-    private @NonNull boolean[] mSkipRecoveryActionArray;
+    @NonNull
+    private boolean[] mSkipRecoveryActionArray;
 
     /**
      * The content URI for the DSRM recovery actions.
@@ -224,12 +228,13 @@
             Settings.Global.DSRM_DURATION_MILLIS);
 
 
-    private DataStallRecoveryManagerCallback mDataStallRecoveryManagerCallback;
+    private final DataStallRecoveryManagerCallback mDataStallRecoveryManagerCallback;
 
     private final DataStallRecoveryStats mStats;
 
     /** The number of milliseconds to wait for the DSRM prediction to complete. */
-    private @ElapsedRealtimeLong long mPredictWaitingMillis = 0L;
+    @ElapsedRealtimeLong
+    private long mPredictWaitingMillis = 0L;
 
     /**
      * The data stall recovery manager callback. Note this is only used for passing information
@@ -476,9 +481,8 @@
 
             // Copy the values from the durationMillisArray array to the
             // mDataStallRecoveryDelayMillisArray array.
-            for (int i = 0; i < minLength; i++) {
-                mDataStallRecoveryDelayMillisArray[i] = durationMillisArray[i];
-            }
+            System.arraycopy(durationMillisArray, 0, mDataStallRecoveryDelayMillisArray,
+                    0, minLength);
             log("DataStallRecoveryDelayMillis: "
                     + Arrays.toString(mDataStallRecoveryDelayMillisArray));
             mPredictWaitingMillis = DSRM_PREDICT_WAITING_MILLIS;
@@ -574,7 +578,7 @@
         if (isValid) {
             if (mFeatureFlags.dsrsDiagnosticsEnabled()) {
                 // Broadcast intent that data stall recovered.
-                broadcastDataStallDetected(getRecoveryAction());
+                broadcastDataStallDetected(mLastAction);
             }
             reset();
         } else if (isRecoveryNeeded(true)) {
@@ -695,7 +699,7 @@
         // Get the information for DSRS state
         final boolean isRecovered = !mDataStalled;
         final int duration = (int) (SystemClock.elapsedRealtime() - mDataStallStartMs);
-        final @RecoveredReason int reason = getRecoveredReason(mIsValidNetwork);
+        @RecoveredReason final int reason = getRecoveredReason(mIsValidNetwork);
         final int durationOfAction = (int) getDurationOfCurrentRecoveryMs();
         if (mFeatureFlags.dsrsDiagnosticsEnabled()) {
             log("mValidationCount=" + mValidationCount
@@ -723,7 +727,7 @@
     private void cleanUpDataNetwork() {
         log("cleanUpDataNetwork: notify clean up data network");
         mDataStallRecoveryManagerCallback.invokeFromExecutor(
-                () -> mDataStallRecoveryManagerCallback.onDataStallReestablishInternet());
+                mDataStallRecoveryManagerCallback::onDataStallReestablishInternet);
     }
 
     /** Recovery Action: RECOVERY_ACTION_RADIO_RESTART */
@@ -828,7 +832,7 @@
     private void setNetworkValidationState(boolean isValid) {
         boolean isLogNeeded = false;
         int timeDuration = 0;
-        int timeDurationOfCurrentAction = 0;
+        int timeDurationOfCurrentAction;
         boolean isFirstDataStall = false;
         boolean isFirstValidationAfterDoRecovery = false;
         @RecoveredReason int reason = getRecoveredReason(isValid);
@@ -872,7 +876,7 @@
                     isFirstValidationAfterDoRecovery, timeDurationOfCurrentAction);
             logl(
                     "data stall: "
-                    + (isFirstDataStall == true ? "start" : isValid == false ? "in process" : "end")
+                    + (isFirstDataStall ? "start" : !isValid ? "in process" : "end")
                     + ", lastaction="
                     + recoveryActionToString(mLastAction)
                     + ", isRecovered="
@@ -902,9 +906,6 @@
             if (mLastAction <= RECOVERY_ACTION_CLEANUP) {
                 ret = RECOVERED_REASON_MODEM;
             }
-            if (mLastAction > RECOVERY_ACTION_CLEANUP) {
-                ret = RECOVERED_REASON_DSRM;
-            }
             if (mIsAirPlaneModeEnableDuringDataStall) {
                 ret = RECOVERED_REASON_USER;
             }
@@ -964,19 +965,15 @@
      * @param reason The recovered reason.
      * @return The recovered reason in string format.
      */
-    private static @NonNull String recoveredReasonToString(@RecoveredReason int reason) {
-        switch (reason) {
-            case RECOVERED_REASON_NONE:
-                return "RECOVERED_REASON_NONE";
-            case RECOVERED_REASON_DSRM:
-                return "RECOVERED_REASON_DSRM";
-            case RECOVERED_REASON_MODEM:
-                return "RECOVERED_REASON_MODEM";
-            case RECOVERED_REASON_USER:
-                return "RECOVERED_REASON_USER";
-            default:
-                return "Unknown(" + reason + ")";
-        }
+    @NonNull
+    private static String recoveredReasonToString(@RecoveredReason int reason) {
+        return switch (reason) {
+            case RECOVERED_REASON_NONE -> "RECOVERED_REASON_NONE";
+            case RECOVERED_REASON_DSRM -> "RECOVERED_REASON_DSRM";
+            case RECOVERED_REASON_MODEM -> "RECOVERED_REASON_MODEM";
+            case RECOVERED_REASON_USER -> "RECOVERED_REASON_USER";
+            default -> "Unknown(" + reason + ")";
+        };
     }
 
     /**
@@ -985,17 +982,14 @@
      * @param state The radio power state
      * @return The radio power state in string format.
      */
-    private static @NonNull String radioPowerStateToString(@RadioPowerState int state) {
-        switch (state) {
-            case TelephonyManager.RADIO_POWER_OFF:
-                return "RADIO_POWER_OFF";
-            case TelephonyManager.RADIO_POWER_ON:
-                return "RADIO_POWER_ON";
-            case TelephonyManager.RADIO_POWER_UNAVAILABLE:
-                return "RADIO_POWER_UNAVAILABLE";
-            default:
-                return "Unknown(" + state + ")";
-        }
+    @NonNull
+    private static String radioPowerStateToString(@RadioPowerState int state) {
+        return switch (state) {
+            case TelephonyManager.RADIO_POWER_OFF -> "RADIO_POWER_OFF";
+            case TelephonyManager.RADIO_POWER_ON -> "RADIO_POWER_ON";
+            case TelephonyManager.RADIO_POWER_UNAVAILABLE -> "RADIO_POWER_UNAVAILABLE";
+            default -> "Unknown(" + state + ")";
+        };
     }
 
     /**
@@ -1004,19 +998,15 @@
      * @param action The recovery action
      * @return The recovery action in string format.
      */
-    private static @NonNull String recoveryActionToString(@RecoveryAction int action) {
-        switch (action) {
-            case RECOVERY_ACTION_GET_DATA_CALL_LIST:
-                return "RECOVERY_ACTION_GET_DATA_CALL_LIST";
-            case RECOVERY_ACTION_CLEANUP:
-                return "RECOVERY_ACTION_CLEANUP";
-            case RECOVERY_ACTION_RADIO_RESTART:
-                return "RECOVERY_ACTION_RADIO_RESTART";
-            case RECOVERY_ACTION_RESET_MODEM:
-                return "RECOVERY_ACTION_RESET_MODEM";
-            default:
-                return "Unknown(" + action + ")";
-        }
+    @NonNull
+    private static String recoveryActionToString(@RecoveryAction int action) {
+        return switch (action) {
+            case RECOVERY_ACTION_GET_DATA_CALL_LIST -> "RECOVERY_ACTION_GET_DATA_CALL_LIST";
+            case RECOVERY_ACTION_CLEANUP -> "RECOVERY_ACTION_CLEANUP";
+            case RECOVERY_ACTION_RADIO_RESTART -> "RECOVERY_ACTION_RADIO_RESTART";
+            case RECOVERY_ACTION_RESET_MODEM -> "RECOVERY_ACTION_RESET_MODEM";
+            default -> "Unknown(" + action + ")";
+        };
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/data/DataUtils.java b/src/java/com/android/internal/telephony/data/DataUtils.java
index 0dda7b5..20da97f 100644
--- a/src/java/com/android/internal/telephony/data/DataUtils.java
+++ b/src/java/com/android/internal/telephony/data/DataUtils.java
@@ -27,6 +27,7 @@
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
 import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
 import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.Annotation.ConnectivityTransport;
 import android.telephony.Annotation.DataActivityType;
 import android.telephony.Annotation.NetCapability;
 import android.telephony.Annotation.NetworkType;
@@ -36,11 +37,11 @@
 import android.telephony.data.ApnSetting.ApnType;
 import android.telephony.data.DataCallResponse;
 import android.telephony.data.DataCallResponse.LinkStatus;
-import android.telephony.data.DataProfile;
 import android.telephony.ims.feature.ImsFeature;
 import android.util.ArrayMap;
 
 import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.telephony.Rlog;
 
 import java.text.SimpleDateFormat;
@@ -48,7 +49,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -59,6 +59,7 @@
  * This class contains all the utility methods used by telephony data stack.
  */
 public class DataUtils {
+    public static final int NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED = 37;
     /** The time format for converting time to readable string. */
     private static final SimpleDateFormat TIME_FORMAT =
             new SimpleDateFormat("HH:mm:ss.SSS", Locale.US);
@@ -70,32 +71,30 @@
      * @param capabilityString The capability in string format
      * @return The network capability. -1 if not found.
      */
-    public static @NetCapability int getNetworkCapabilityFromString(
-            @NonNull String capabilityString) {
-        switch (capabilityString.toUpperCase(Locale.ROOT)) {
-            case "MMS": return NetworkCapabilities.NET_CAPABILITY_MMS;
-            case "SUPL": return NetworkCapabilities.NET_CAPABILITY_SUPL;
-            case "DUN": return NetworkCapabilities.NET_CAPABILITY_DUN;
-            case "FOTA": return NetworkCapabilities.NET_CAPABILITY_FOTA;
-            case "IMS": return NetworkCapabilities.NET_CAPABILITY_IMS;
-            case "CBS": return NetworkCapabilities.NET_CAPABILITY_CBS;
-            case "XCAP": return NetworkCapabilities.NET_CAPABILITY_XCAP;
-            case "EIMS": return NetworkCapabilities.NET_CAPABILITY_EIMS;
-            case "INTERNET": return NetworkCapabilities.NET_CAPABILITY_INTERNET;
-            case "MCX": return NetworkCapabilities.NET_CAPABILITY_MCX;
-            case "VSIM": return NetworkCapabilities.NET_CAPABILITY_VSIM;
-            case "BIP" : return NetworkCapabilities.NET_CAPABILITY_BIP;
-            case "ENTERPRISE": return NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
-            case "PRIORITIZE_BANDWIDTH":
-                return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
-            case "PRIORITIZE_LATENCY":
-                return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
-            case "RCS":
-                return NetworkCapabilities.NET_CAPABILITY_RCS;
-            default:
+    @NetCapability
+    public static int getNetworkCapabilityFromString(@NonNull String capabilityString) {
+        return switch (capabilityString.toUpperCase(Locale.ROOT)) {
+            case "MMS" -> NetworkCapabilities.NET_CAPABILITY_MMS;
+            case "SUPL" -> NetworkCapabilities.NET_CAPABILITY_SUPL;
+            case "DUN" -> NetworkCapabilities.NET_CAPABILITY_DUN;
+            case "FOTA" -> NetworkCapabilities.NET_CAPABILITY_FOTA;
+            case "IMS" -> NetworkCapabilities.NET_CAPABILITY_IMS;
+            case "CBS" -> NetworkCapabilities.NET_CAPABILITY_CBS;
+            case "XCAP" -> NetworkCapabilities.NET_CAPABILITY_XCAP;
+            case "EIMS" -> NetworkCapabilities.NET_CAPABILITY_EIMS;
+            case "INTERNET" -> NetworkCapabilities.NET_CAPABILITY_INTERNET;
+            case "MCX" -> NetworkCapabilities.NET_CAPABILITY_MCX;
+            case "VSIM" -> NetworkCapabilities.NET_CAPABILITY_VSIM;
+            case "BIP" -> NetworkCapabilities.NET_CAPABILITY_BIP;
+            case "ENTERPRISE" -> NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
+            case "PRIORITIZE_BANDWIDTH" -> NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
+            case "PRIORITIZE_LATENCY" -> NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
+            case "RCS" -> NetworkCapabilities.NET_CAPABILITY_RCS;
+            default -> {
                 loge("Illegal network capability: " + capabilityString);
-                return -1;
-        }
+                yield -1;
+            }
+        };
     }
 
     /**
@@ -106,7 +105,8 @@
      * @param capabilitiesString capability strings joined by {@code |}
      * @return Set of capabilities
      */
-    public static @NetCapability Set<Integer> getNetworkCapabilitiesFromString(
+    @NetCapability
+    public static Set<Integer> getNetworkCapabilitiesFromString(
             @NonNull String capabilitiesString) {
         // e.g. "IMS|" is not allowed
         if (!capabilitiesString.matches("(\\s*[a-zA-Z_]+\\s*)(\\|\\s*[a-zA-Z_]+\\s*)*")) {
@@ -120,69 +120,108 @@
 
     /**
      * Convert a network capability to string.
-     *
+     * <p>
      * This is for debugging and logging purposes only.
      *
      * @param netCap Network capability.
      * @return Network capability in string format.
      */
-    public static @NonNull String networkCapabilityToString(@NetCapability int netCap) {
-        switch (netCap) {
-            case NetworkCapabilities.NET_CAPABILITY_MMS:                  return "MMS";
-            case NetworkCapabilities.NET_CAPABILITY_SUPL:                 return "SUPL";
-            case NetworkCapabilities.NET_CAPABILITY_DUN:                  return "DUN";
-            case NetworkCapabilities.NET_CAPABILITY_FOTA:                 return "FOTA";
-            case NetworkCapabilities.NET_CAPABILITY_IMS:                  return "IMS";
-            case NetworkCapabilities.NET_CAPABILITY_CBS:                  return "CBS";
-            case NetworkCapabilities.NET_CAPABILITY_WIFI_P2P:             return "WIFI_P2P";
-            case NetworkCapabilities.NET_CAPABILITY_IA:                   return "IA";
-            case NetworkCapabilities.NET_CAPABILITY_RCS:                  return "RCS";
-            case NetworkCapabilities.NET_CAPABILITY_XCAP:                 return "XCAP";
-            case NetworkCapabilities.NET_CAPABILITY_EIMS:                 return "EIMS";
-            case NetworkCapabilities.NET_CAPABILITY_NOT_METERED:          return "NOT_METERED";
-            case NetworkCapabilities.NET_CAPABILITY_INTERNET:             return "INTERNET";
-            case NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED:       return "NOT_RESTRICTED";
-            case NetworkCapabilities.NET_CAPABILITY_TRUSTED:              return "TRUSTED";
-            case NetworkCapabilities.NET_CAPABILITY_NOT_VPN:              return "NOT_VPN";
-            case NetworkCapabilities.NET_CAPABILITY_VALIDATED:            return "VALIDATED";
-            case NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL:       return "CAPTIVE_PORTAL";
-            case NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING:          return "NOT_ROAMING";
-            case NetworkCapabilities.NET_CAPABILITY_FOREGROUND:           return "FOREGROUND";
-            case NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED:        return "NOT_CONGESTED";
-            case NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED:        return "NOT_SUSPENDED";
-            case NetworkCapabilities.NET_CAPABILITY_OEM_PAID:             return "OEM_PAID";
-            case NetworkCapabilities.NET_CAPABILITY_MCX:                  return "MCX";
-            case NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY:
-                return "PARTIAL_CONNECTIVITY";
-            case NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED:
-                return "TEMPORARILY_NOT_METERED";
-            case NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE:          return "OEM_PRIVATE";
-            case NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL:     return "VEHICLE_INTERNAL";
-            case NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED:      return "NOT_VCN_MANAGED";
-            case NetworkCapabilities.NET_CAPABILITY_ENTERPRISE:           return "ENTERPRISE";
-            case NetworkCapabilities.NET_CAPABILITY_VSIM:                 return "VSIM";
-            case NetworkCapabilities.NET_CAPABILITY_BIP:                  return "BIP";
-            case NetworkCapabilities.NET_CAPABILITY_HEAD_UNIT:            return "HEAD_UNIT";
-            case NetworkCapabilities.NET_CAPABILITY_MMTEL:                return "MMTEL";
-            case NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY:
-                return "PRIORITIZE_LATENCY";
-            case NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH:
-                return "PRIORITIZE_BANDWIDTH";
-            default:
+    @NonNull
+    public static String networkCapabilityToString(@NetCapability int netCap) {
+        return switch (netCap) {
+            case NetworkCapabilities.NET_CAPABILITY_MMS -> "MMS";
+            case NetworkCapabilities.NET_CAPABILITY_SUPL -> "SUPL";
+            case NetworkCapabilities.NET_CAPABILITY_DUN -> "DUN";
+            case NetworkCapabilities.NET_CAPABILITY_FOTA -> "FOTA";
+            case NetworkCapabilities.NET_CAPABILITY_IMS -> "IMS";
+            case NetworkCapabilities.NET_CAPABILITY_CBS -> "CBS";
+            case NetworkCapabilities.NET_CAPABILITY_WIFI_P2P -> "WIFI_P2P";
+            case NetworkCapabilities.NET_CAPABILITY_IA -> "IA";
+            case NetworkCapabilities.NET_CAPABILITY_RCS -> "RCS";
+            case NetworkCapabilities.NET_CAPABILITY_XCAP -> "XCAP";
+            case NetworkCapabilities.NET_CAPABILITY_EIMS -> "EIMS";
+            case NetworkCapabilities.NET_CAPABILITY_NOT_METERED -> "NOT_METERED";
+            case NetworkCapabilities.NET_CAPABILITY_INTERNET -> "INTERNET";
+            case NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED -> "NOT_RESTRICTED";
+            case NetworkCapabilities.NET_CAPABILITY_TRUSTED -> "TRUSTED";
+            case NetworkCapabilities.NET_CAPABILITY_NOT_VPN -> "NOT_VPN";
+            case NetworkCapabilities.NET_CAPABILITY_VALIDATED -> "VALIDATED";
+            case NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL -> "CAPTIVE_PORTAL";
+            case NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING -> "NOT_ROAMING";
+            case NetworkCapabilities.NET_CAPABILITY_FOREGROUND -> "FOREGROUND";
+            case NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED -> "NOT_CONGESTED";
+            case NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED -> "NOT_SUSPENDED";
+            case NetworkCapabilities.NET_CAPABILITY_OEM_PAID -> "OEM_PAID";
+            case NetworkCapabilities.NET_CAPABILITY_MCX -> "MCX";
+            case NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY -> "PARTIAL_CONNECTIVITY";
+            case NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED ->
+                    "TEMPORARILY_NOT_METERED";
+            case NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE -> "OEM_PRIVATE";
+            case NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL -> "VEHICLE_INTERNAL";
+            case NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED -> "NOT_VCN_MANAGED";
+            case NetworkCapabilities.NET_CAPABILITY_ENTERPRISE -> "ENTERPRISE";
+            case NetworkCapabilities.NET_CAPABILITY_VSIM -> "VSIM";
+            case NetworkCapabilities.NET_CAPABILITY_BIP -> "BIP";
+            case NetworkCapabilities.NET_CAPABILITY_HEAD_UNIT -> "HEAD_UNIT";
+            case NetworkCapabilities.NET_CAPABILITY_MMTEL -> "MMTEL";
+            case NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY -> "PRIORITIZE_LATENCY";
+            case NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH -> "PRIORITIZE_BANDWIDTH";
+            case NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED -> "NOT_BANDWIDTH_CONSTRAINED";
+            default -> {
                 loge("Unknown network capability(" + netCap + ")");
-                return "Unknown(" + netCap + ")";
-        }
+                yield "Unknown(" + netCap + ")";
+            }
+        };
+    }
+
+    /**
+     * Concat an array of {@link NetworkCapabilities.Transport} in string format.
+     *
+     * @param transports an array of connectivity transports
+     * @return a string of the array of transports.
+     */
+    @NonNull
+    public static String connectivityTransportsToString(
+            @NonNull @ConnectivityTransport int[] transports) {
+        return Arrays.stream(transports).mapToObj(DataUtils::connectivityTransportToString)
+                .collect(Collectors.joining("|"));
+    }
+
+    /**
+     * Convert a {@link NetworkCapabilities.Transport} to a string.
+     *
+     * @param transport the connectivity transport
+     * @return the transport in string
+     */
+    @NonNull
+    public static String connectivityTransportToString(
+            @ConnectivityTransport int transport) {
+        return switch (transport) {
+            case NetworkCapabilities.TRANSPORT_CELLULAR -> "CELLULAR";
+            case NetworkCapabilities.TRANSPORT_WIFI -> "WIFI";
+            case NetworkCapabilities.TRANSPORT_BLUETOOTH -> "BLUETOOTH";
+            case NetworkCapabilities.TRANSPORT_ETHERNET -> "ETHERNET";
+            case NetworkCapabilities.TRANSPORT_VPN -> "VPN";
+            case NetworkCapabilities.TRANSPORT_WIFI_AWARE -> "WIFI_AWARE";
+            case NetworkCapabilities.TRANSPORT_LOWPAN -> "LOWPAN";
+            case NetworkCapabilities.TRANSPORT_TEST -> "TEST";
+            case NetworkCapabilities.TRANSPORT_USB -> "USB";
+            case NetworkCapabilities.TRANSPORT_THREAD -> "THREAD";
+            case NetworkCapabilities.TRANSPORT_SATELLITE -> "SATELLITE";
+            default -> "Unknown(" + transport + ")";
+        };
     }
 
     /**
      * Convert network capabilities to string.
-     *
+     * <p>
      * This is for debugging and logging purposes only.
      *
      * @param netCaps Network capabilities.
      * @return Network capabilities in string format.
      */
-    public static @NonNull String networkCapabilitiesToString(
+    @NonNull
+    public static String networkCapabilitiesToString(
             @NetCapability @Nullable Collection<Integer> netCaps) {
         if (netCaps == null || netCaps.isEmpty()) return "";
         return "[" + netCaps.stream()
@@ -192,13 +231,14 @@
 
     /**
      * Convert network capabilities to string.
-     *
+     * <p>
      * This is for debugging and logging purposes only.
      *
      * @param netCaps Network capabilities.
      * @return Network capabilities in string format.
      */
-    public static @NonNull String networkCapabilitiesToString(@NetCapability int[] netCaps) {
+    @NonNull
+    public static String networkCapabilitiesToString(@NetCapability int[] netCaps) {
         if (netCaps == null) return "";
         return "[" + Arrays.stream(netCaps)
                 .mapToObj(DataUtils::networkCapabilityToString)
@@ -211,14 +251,16 @@
      * @param status The validation status.
      * @return The validation status in string format.
      */
-    public static @NonNull String validationStatusToString(@ValidationStatus int status) {
-        switch (status) {
-            case NetworkAgent.VALIDATION_STATUS_VALID: return "VALID";
-            case NetworkAgent.VALIDATION_STATUS_NOT_VALID: return "INVALID";
-            default:
+    @NonNull
+    public static String validationStatusToString(@ValidationStatus int status) {
+        return switch (status) {
+            case NetworkAgent.VALIDATION_STATUS_VALID -> "VALID";
+            case NetworkAgent.VALIDATION_STATUS_NOT_VALID -> "INVALID";
+            default -> {
                 loge("Unknown validation status(" + status + ")");
-                return "UNKNOWN(" + status + ")";
-        }
+                yield "UNKNOWN(" + status + ")";
+            }
+        };
     }
 
     /**
@@ -227,41 +269,26 @@
      * @param networkCapability Network capability.
      * @return APN type.
      */
-    public static @ApnType int networkCapabilityToApnType(@NetCapability int networkCapability) {
-        switch (networkCapability) {
-            case NetworkCapabilities.NET_CAPABILITY_MMS:
-                return ApnSetting.TYPE_MMS;
-            case NetworkCapabilities.NET_CAPABILITY_SUPL:
-                return ApnSetting.TYPE_SUPL;
-            case NetworkCapabilities.NET_CAPABILITY_DUN:
-                return ApnSetting.TYPE_DUN;
-            case NetworkCapabilities.NET_CAPABILITY_FOTA:
-                return ApnSetting.TYPE_FOTA;
-            case NetworkCapabilities.NET_CAPABILITY_IMS:
-                return ApnSetting.TYPE_IMS;
-            case NetworkCapabilities.NET_CAPABILITY_CBS:
-                return ApnSetting.TYPE_CBS;
-            case NetworkCapabilities.NET_CAPABILITY_XCAP:
-                return ApnSetting.TYPE_XCAP;
-            case NetworkCapabilities.NET_CAPABILITY_EIMS:
-                return ApnSetting.TYPE_EMERGENCY;
-            case NetworkCapabilities.NET_CAPABILITY_INTERNET:
-                return ApnSetting.TYPE_DEFAULT;
-            case NetworkCapabilities.NET_CAPABILITY_MCX:
-                return ApnSetting.TYPE_MCX;
-            case NetworkCapabilities.NET_CAPABILITY_IA:
-                return ApnSetting.TYPE_IA;
-            case NetworkCapabilities.NET_CAPABILITY_ENTERPRISE:
-                return ApnSetting.TYPE_ENTERPRISE;
-            case NetworkCapabilities.NET_CAPABILITY_VSIM:
-                return ApnSetting.TYPE_VSIM;
-            case NetworkCapabilities.NET_CAPABILITY_BIP:
-                return ApnSetting.TYPE_BIP;
-            case NetworkCapabilities.NET_CAPABILITY_RCS:
-                return ApnSetting.TYPE_RCS;
-            default:
-                return ApnSetting.TYPE_NONE;
-        }
+    @ApnType
+    public static int networkCapabilityToApnType(@NetCapability int networkCapability) {
+        return switch (networkCapability) {
+            case NetworkCapabilities.NET_CAPABILITY_MMS -> ApnSetting.TYPE_MMS;
+            case NetworkCapabilities.NET_CAPABILITY_SUPL -> ApnSetting.TYPE_SUPL;
+            case NetworkCapabilities.NET_CAPABILITY_DUN -> ApnSetting.TYPE_DUN;
+            case NetworkCapabilities.NET_CAPABILITY_FOTA -> ApnSetting.TYPE_FOTA;
+            case NetworkCapabilities.NET_CAPABILITY_IMS -> ApnSetting.TYPE_IMS;
+            case NetworkCapabilities.NET_CAPABILITY_CBS -> ApnSetting.TYPE_CBS;
+            case NetworkCapabilities.NET_CAPABILITY_XCAP -> ApnSetting.TYPE_XCAP;
+            case NetworkCapabilities.NET_CAPABILITY_EIMS -> ApnSetting.TYPE_EMERGENCY;
+            case NetworkCapabilities.NET_CAPABILITY_INTERNET -> ApnSetting.TYPE_DEFAULT;
+            case NetworkCapabilities.NET_CAPABILITY_MCX -> ApnSetting.TYPE_MCX;
+            case NetworkCapabilities.NET_CAPABILITY_IA -> ApnSetting.TYPE_IA;
+            case NetworkCapabilities.NET_CAPABILITY_ENTERPRISE -> ApnSetting.TYPE_ENTERPRISE;
+            case NetworkCapabilities.NET_CAPABILITY_VSIM -> ApnSetting.TYPE_VSIM;
+            case NetworkCapabilities.NET_CAPABILITY_BIP -> ApnSetting.TYPE_BIP;
+            case NetworkCapabilities.NET_CAPABILITY_RCS -> ApnSetting.TYPE_RCS;
+            default -> ApnSetting.TYPE_NONE;
+        };
     }
 
     /**
@@ -270,41 +297,26 @@
      * @param apnType APN type.
      * @return Network capability.
      */
-    public static @NetCapability int apnTypeToNetworkCapability(@ApnType int apnType) {
-        switch (apnType) {
-            case ApnSetting.TYPE_MMS:
-                return NetworkCapabilities.NET_CAPABILITY_MMS;
-            case ApnSetting.TYPE_SUPL:
-                return NetworkCapabilities.NET_CAPABILITY_SUPL;
-            case ApnSetting.TYPE_DUN:
-                return NetworkCapabilities.NET_CAPABILITY_DUN;
-            case ApnSetting.TYPE_FOTA:
-                return NetworkCapabilities.NET_CAPABILITY_FOTA;
-            case ApnSetting.TYPE_IMS:
-                return NetworkCapabilities.NET_CAPABILITY_IMS;
-            case ApnSetting.TYPE_CBS:
-                return NetworkCapabilities.NET_CAPABILITY_CBS;
-            case ApnSetting.TYPE_XCAP:
-                return NetworkCapabilities.NET_CAPABILITY_XCAP;
-            case ApnSetting.TYPE_EMERGENCY:
-                return NetworkCapabilities.NET_CAPABILITY_EIMS;
-            case ApnSetting.TYPE_DEFAULT:
-                return NetworkCapabilities.NET_CAPABILITY_INTERNET;
-            case ApnSetting.TYPE_MCX:
-                return NetworkCapabilities.NET_CAPABILITY_MCX;
-            case ApnSetting.TYPE_IA:
-                return NetworkCapabilities.NET_CAPABILITY_IA;
-            case ApnSetting.TYPE_BIP:
-                return NetworkCapabilities.NET_CAPABILITY_BIP;
-            case ApnSetting.TYPE_VSIM:
-                return NetworkCapabilities.NET_CAPABILITY_VSIM;
-            case ApnSetting.TYPE_ENTERPRISE:
-                return NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
-            case ApnSetting.TYPE_RCS:
-                return NetworkCapabilities.NET_CAPABILITY_RCS;
-            default:
-                return -1;
-        }
+    @NetCapability
+    public static int apnTypeToNetworkCapability(@ApnType int apnType) {
+        return switch (apnType) {
+            case ApnSetting.TYPE_MMS -> NetworkCapabilities.NET_CAPABILITY_MMS;
+            case ApnSetting.TYPE_SUPL -> NetworkCapabilities.NET_CAPABILITY_SUPL;
+            case ApnSetting.TYPE_DUN -> NetworkCapabilities.NET_CAPABILITY_DUN;
+            case ApnSetting.TYPE_FOTA -> NetworkCapabilities.NET_CAPABILITY_FOTA;
+            case ApnSetting.TYPE_IMS -> NetworkCapabilities.NET_CAPABILITY_IMS;
+            case ApnSetting.TYPE_CBS -> NetworkCapabilities.NET_CAPABILITY_CBS;
+            case ApnSetting.TYPE_XCAP -> NetworkCapabilities.NET_CAPABILITY_XCAP;
+            case ApnSetting.TYPE_EMERGENCY -> NetworkCapabilities.NET_CAPABILITY_EIMS;
+            case ApnSetting.TYPE_DEFAULT -> NetworkCapabilities.NET_CAPABILITY_INTERNET;
+            case ApnSetting.TYPE_MCX -> NetworkCapabilities.NET_CAPABILITY_MCX;
+            case ApnSetting.TYPE_IA -> NetworkCapabilities.NET_CAPABILITY_IA;
+            case ApnSetting.TYPE_BIP -> NetworkCapabilities.NET_CAPABILITY_BIP;
+            case ApnSetting.TYPE_VSIM -> NetworkCapabilities.NET_CAPABILITY_VSIM;
+            case ApnSetting.TYPE_ENTERPRISE -> NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
+            case ApnSetting.TYPE_RCS -> NetworkCapabilities.NET_CAPABILITY_RCS;
+            default -> -1;
+        };
     }
 
     /**
@@ -313,37 +325,26 @@
      * @param networkType The network type.
      * @return The access network type.
      */
-    public static @RadioAccessNetworkType int networkTypeToAccessNetworkType(
-            @NetworkType int networkType) {
-        switch (networkType) {
-            case TelephonyManager.NETWORK_TYPE_GPRS:
-            case TelephonyManager.NETWORK_TYPE_EDGE:
-            case TelephonyManager.NETWORK_TYPE_GSM:
-                return AccessNetworkType.GERAN;
-            case TelephonyManager.NETWORK_TYPE_UMTS:
-            case TelephonyManager.NETWORK_TYPE_HSDPA:
-            case TelephonyManager.NETWORK_TYPE_HSPAP:
-            case TelephonyManager.NETWORK_TYPE_HSUPA:
-            case TelephonyManager.NETWORK_TYPE_HSPA:
-            case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
-                return AccessNetworkType.UTRAN;
-            case TelephonyManager.NETWORK_TYPE_CDMA:
-            case TelephonyManager.NETWORK_TYPE_EVDO_0:
-            case TelephonyManager.NETWORK_TYPE_EVDO_A:
-            case TelephonyManager.NETWORK_TYPE_EVDO_B:
-            case TelephonyManager.NETWORK_TYPE_1xRTT:
-            case TelephonyManager.NETWORK_TYPE_EHRPD:
-                return AccessNetworkType.CDMA2000;
-            case TelephonyManager.NETWORK_TYPE_LTE:
-            case TelephonyManager.NETWORK_TYPE_LTE_CA:
-                return AccessNetworkType.EUTRAN;
-            case TelephonyManager.NETWORK_TYPE_IWLAN:
-                return AccessNetworkType.IWLAN;
-            case TelephonyManager.NETWORK_TYPE_NR:
-                return AccessNetworkType.NGRAN;
-            default:
-                return AccessNetworkType.UNKNOWN;
-        }
+    @RadioAccessNetworkType
+    public static int networkTypeToAccessNetworkType(@NetworkType int networkType) {
+        return switch (networkType) {
+            case TelephonyManager.NETWORK_TYPE_GPRS, TelephonyManager.NETWORK_TYPE_EDGE,
+                    TelephonyManager.NETWORK_TYPE_GSM ->
+                    AccessNetworkType.GERAN;
+            case TelephonyManager.NETWORK_TYPE_UMTS, TelephonyManager.NETWORK_TYPE_HSDPA,
+                    TelephonyManager.NETWORK_TYPE_HSPAP, TelephonyManager.NETWORK_TYPE_HSUPA,
+                    TelephonyManager.NETWORK_TYPE_HSPA, TelephonyManager.NETWORK_TYPE_TD_SCDMA ->
+                    AccessNetworkType.UTRAN;
+            case TelephonyManager.NETWORK_TYPE_CDMA, TelephonyManager.NETWORK_TYPE_EVDO_0,
+                    TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyManager.NETWORK_TYPE_EVDO_B,
+                    TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyManager.NETWORK_TYPE_EHRPD ->
+                    AccessNetworkType.CDMA2000;
+            case TelephonyManager.NETWORK_TYPE_LTE, TelephonyManager.NETWORK_TYPE_LTE_CA ->
+                    AccessNetworkType.EUTRAN;
+            case TelephonyManager.NETWORK_TYPE_IWLAN -> AccessNetworkType.IWLAN;
+            case TelephonyManager.NETWORK_TYPE_NR -> AccessNetworkType.NGRAN;
+            default -> AccessNetworkType.UNKNOWN;
+        };
     }
 
     /**
@@ -352,7 +353,8 @@
      * @param elapsedTime The elapsed time retrieved from {@link SystemClock#elapsedRealtime()}.
      * @return The string format time.
      */
-    public static @NonNull String elapsedTimeToString(@ElapsedRealtimeLong long elapsedTime) {
+    @NonNull
+    public static String elapsedTimeToString(@ElapsedRealtimeLong long elapsedTime) {
         return (elapsedTime != 0) ? systemTimeToString(System.currentTimeMillis()
                 - SystemClock.elapsedRealtime() + elapsedTime) : "never";
     }
@@ -363,7 +365,8 @@
      * @param systemTime The system time retrieved from {@link System#currentTimeMillis()}.
      * @return The string format time.
      */
-    public static @NonNull String systemTimeToString(@CurrentTimeMillisLong long systemTime) {
+    @NonNull
+    public static String systemTimeToString(@CurrentTimeMillisLong long systemTime) {
         return (systemTime != 0) ? TIME_FORMAT.format(systemTime) : "never";
     }
 
@@ -373,68 +376,75 @@
      * @param imsFeature IMS feature.
      * @return IMS feature in string format.
      */
-    public static @NonNull String imsFeatureToString(@ImsFeature.FeatureType int imsFeature) {
-        switch (imsFeature) {
-            case ImsFeature.FEATURE_MMTEL: return "MMTEL";
-            case ImsFeature.FEATURE_RCS: return "RCS";
-            default:
+    @NonNull
+    public static String imsFeatureToString(@ImsFeature.FeatureType int imsFeature) {
+        return switch (imsFeature) {
+            case ImsFeature.FEATURE_MMTEL -> "MMTEL";
+            case ImsFeature.FEATURE_RCS -> "RCS";
+            default -> {
                 loge("Unknown IMS feature(" + imsFeature + ")");
-                return "Unknown(" + imsFeature + ")";
-        }
-    }
-
-    /**
-     * Get the highest priority supported network capability from the specified data profile.
-     *
-     * @param dataConfigManager The data config that contains network priority information.
-     * @param dataProfile The data profile
-     * @return The highest priority network capability. -1 if cannot find one.
-     */
-    public static @NetCapability int getHighestPriorityNetworkCapabilityFromDataProfile(
-            @NonNull DataConfigManager dataConfigManager, @NonNull DataProfile dataProfile) {
-        if (dataProfile.getApnSetting() == null
-                || dataProfile.getApnSetting().getApnTypes().isEmpty()) return -1;
-        return dataProfile.getApnSetting().getApnTypes().stream()
-                .map(DataUtils::apnTypeToNetworkCapability)
-                .sorted(Comparator.comparing(dataConfigManager::getNetworkCapabilityPriority)
-                        .reversed())
-                .collect(Collectors.toList())
-                .get(0);
+                yield "Unknown(" + imsFeature + ")";
+            }
+        };
     }
 
     /**
      * Group the network requests into several list that contains the same network capabilities.
      *
      * @param networkRequestList The provided network requests.
+     * @param featureFlags The feature flag.
+     *
      * @return The network requests after grouping.
      */
-    public static @NonNull List<NetworkRequestList> getGroupedNetworkRequestList(
-            @NonNull NetworkRequestList networkRequestList) {
-        // Key is the capabilities set.
-        Map<Set<Integer>, NetworkRequestList> requestsMap = new ArrayMap<>();
-        for (TelephonyNetworkRequest networkRequest : networkRequestList) {
-            requestsMap.computeIfAbsent(Arrays.stream(networkRequest.getCapabilities())
-                            .boxed().collect(Collectors.toSet()),
-                    v -> new NetworkRequestList()).add(networkRequest);
-        }
+    @NonNull
+    public static List<NetworkRequestList> getGroupedNetworkRequestList(
+            @NonNull NetworkRequestList networkRequestList, @NonNull FeatureFlags featureFlags) {
         List<NetworkRequestList> requests = new ArrayList<>();
-        // Create separate groups for enterprise requests with different enterprise IDs.
-        for (NetworkRequestList requestList : requestsMap.values()) {
-            List<TelephonyNetworkRequest> enterpriseRequests = requestList.stream()
-                    .filter(request ->
-                            request.hasCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE))
-                    .collect(Collectors.toList());
-            if (enterpriseRequests.isEmpty()) {
-                requests.add(requestList);
-                continue;
+        if (featureFlags.satelliteInternet()) {
+            record NetworkCapabilitiesKey(Set<Integer> caps, Set<Integer> enterpriseIds,
+                                          Set<Integer> transportTypes) { }
+
+            // Key is the combination of capabilities, enterprise ids, and transport types.
+            Map<NetworkCapabilitiesKey, NetworkRequestList> requestsMap = new ArrayMap<>();
+            for (TelephonyNetworkRequest networkRequest : networkRequestList) {
+                requestsMap.computeIfAbsent(new NetworkCapabilitiesKey(
+                                Arrays.stream(networkRequest.getCapabilities())
+                                        .boxed().collect(Collectors.toSet()),
+                                Arrays.stream(networkRequest.getNativeNetworkRequest()
+                                                .getEnterpriseIds())
+                                        .boxed().collect(Collectors.toSet()),
+                                Arrays.stream(networkRequest.getTransportTypes())
+                                        .boxed().collect(Collectors.toSet())
+                                ),
+                        v -> new NetworkRequestList()).add(networkRequest);
             }
-            // Key is the enterprise ID
-            Map<Integer, NetworkRequestList> enterpriseRequestsMap = new ArrayMap<>();
-            for (TelephonyNetworkRequest request : enterpriseRequests) {
-                enterpriseRequestsMap.computeIfAbsent(request.getCapabilityDifferentiator(),
-                        v -> new NetworkRequestList()).add(request);
+            requests.addAll(requestsMap.values());
+        } else {
+            // Key is the capabilities set.
+            Map<Set<Integer>, NetworkRequestList> requestsMap = new ArrayMap<>();
+            for (TelephonyNetworkRequest networkRequest : networkRequestList) {
+                requestsMap.computeIfAbsent(Arrays.stream(networkRequest.getCapabilities())
+                                .boxed().collect(Collectors.toSet()),
+                        v -> new NetworkRequestList()).add(networkRequest);
             }
-            requests.addAll(enterpriseRequestsMap.values());
+            // Create separate groups for enterprise requests with different enterprise IDs.
+            for (NetworkRequestList requestList : requestsMap.values()) {
+                List<TelephonyNetworkRequest> enterpriseRequests = requestList.stream()
+                        .filter(request -> request.hasCapability(
+                                NetworkCapabilities.NET_CAPABILITY_ENTERPRISE))
+                        .toList();
+                if (enterpriseRequests.isEmpty()) {
+                    requests.add(requestList);
+                    continue;
+                }
+                // Key is the enterprise ID
+                Map<Integer, NetworkRequestList> enterpriseRequestsMap = new ArrayMap<>();
+                for (TelephonyNetworkRequest request : enterpriseRequests) {
+                    enterpriseRequestsMap.computeIfAbsent(request.getCapabilityDifferentiator(),
+                            v -> new NetworkRequestList()).add(request);
+                }
+                requests.addAll(enterpriseRequestsMap.values());
+            }
         }
         // Sort the requests so the network request list with higher priority will be at the front.
         return requests.stream()
@@ -450,41 +460,31 @@
      * @param sourceTransport The source transport.
      * @return The target transport.
      */
-    public static @TransportType int getTargetTransport(@TransportType int sourceTransport) {
+    @TransportType
+    public static int getTargetTransport(@TransportType int sourceTransport) {
         return sourceTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
                 ? AccessNetworkConstants.TRANSPORT_TYPE_WLAN
                 : AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
     }
 
     /**
-     * Get the source transport from target transport. This is only used for handover between
-     * IWLAN and cellular scenario.
-     *
-     * @param targetTransport The target transport.
-     * @return The source transport.
-     */
-    public static @TransportType int getSourceTransport(@TransportType int targetTransport) {
-        return targetTransport == AccessNetworkConstants.TRANSPORT_TYPE_WWAN
-                ? AccessNetworkConstants.TRANSPORT_TYPE_WLAN
-                : AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
-    }
-
-    /**
      * Convert link status to string.
      *
      * @param linkStatus The link status.
      * @return The link status in string format.
      */
-    public static @NonNull String linkStatusToString(@LinkStatus int linkStatus) {
-        switch (linkStatus) {
-            case DataCallResponse.LINK_STATUS_UNKNOWN: return "UNKNOWN";
-            case DataCallResponse.LINK_STATUS_INACTIVE: return "INACTIVE";
-            case DataCallResponse.LINK_STATUS_ACTIVE: return "ACTIVE";
-            case DataCallResponse.LINK_STATUS_DORMANT: return "DORMANT";
-            default:
+    @NonNull
+    public static String linkStatusToString(@LinkStatus int linkStatus) {
+        return switch (linkStatus) {
+            case DataCallResponse.LINK_STATUS_UNKNOWN -> "UNKNOWN";
+            case DataCallResponse.LINK_STATUS_INACTIVE -> "INACTIVE";
+            case DataCallResponse.LINK_STATUS_ACTIVE -> "ACTIVE";
+            case DataCallResponse.LINK_STATUS_DORMANT -> "DORMANT";
+            default -> {
                 loge("Unknown link status(" + linkStatus + ")");
-                return "UNKNOWN(" + linkStatus + ")";
-        }
+                yield "UNKNOWN(" + linkStatus + ")";
+            }
+        };
     }
 
     /**
@@ -494,17 +494,12 @@
      * @return {@code true} if the access network type is valid.
      */
     public static boolean isValidAccessNetwork(@RadioAccessNetworkType int accessNetworkType) {
-        switch (accessNetworkType) {
-            case AccessNetworkType.GERAN:
-            case AccessNetworkType.UTRAN:
-            case AccessNetworkType.EUTRAN:
-            case AccessNetworkType.CDMA2000:
-            case AccessNetworkType.IWLAN:
-            case AccessNetworkType.NGRAN:
-                return true;
-            default:
-                return false;
-        }
+        return switch (accessNetworkType) {
+            case AccessNetworkType.GERAN, AccessNetworkType.UTRAN, AccessNetworkType.EUTRAN,
+                    AccessNetworkType.CDMA2000, AccessNetworkType.IWLAN, AccessNetworkType.NGRAN ->
+                    true;
+            default -> false;
+        };
     }
 
     /**
@@ -513,17 +508,19 @@
      * @param dataActivity The data activity.
      * @return The data activity in string format.
      */
-    public static @NonNull String dataActivityToString(@DataActivityType int dataActivity) {
-        switch (dataActivity) {
-            case TelephonyManager.DATA_ACTIVITY_NONE: return "NONE";
-            case TelephonyManager.DATA_ACTIVITY_IN: return "IN";
-            case TelephonyManager.DATA_ACTIVITY_OUT: return "OUT";
-            case TelephonyManager.DATA_ACTIVITY_INOUT: return "INOUT";
-            case TelephonyManager.DATA_ACTIVITY_DORMANT: return "DORMANT";
-            default:
+    @NonNull
+    public static String dataActivityToString(@DataActivityType int dataActivity) {
+        return switch (dataActivity) {
+            case TelephonyManager.DATA_ACTIVITY_NONE -> "NONE";
+            case TelephonyManager.DATA_ACTIVITY_IN -> "IN";
+            case TelephonyManager.DATA_ACTIVITY_OUT -> "OUT";
+            case TelephonyManager.DATA_ACTIVITY_INOUT -> "INOUT";
+            case TelephonyManager.DATA_ACTIVITY_DORMANT -> "DORMANT";
+            default -> {
                 loge("Unknown data activity(" + dataActivity + ")");
-                return "UNKNOWN(" + dataActivity + ")";
-        }
+                yield "UNKNOWN(" + dataActivity + ")";
+            }
+        };
     }
 
     private static void loge(String msg) {
diff --git a/src/java/com/android/internal/telephony/data/KeepaliveStatus.java b/src/java/com/android/internal/telephony/data/KeepaliveStatus.java
index 818de96..7b6c38d 100644
--- a/src/java/com/android/internal/telephony/data/KeepaliveStatus.java
+++ b/src/java/com/android/internal/telephony/data/KeepaliveStatus.java
@@ -23,7 +23,7 @@
 /**
  * This class serves to pass around the parameters of Keepalive session
  * status within the telephony framework.
- *
+ * <p>
  * {@hide}
  */
 public class KeepaliveStatus implements Parcelable {
@@ -54,7 +54,8 @@
      * A status code indicating whether this Keepalive session is
      * active, inactive, or pending activation
      */
-    public final @KeepaliveStatusCode int statusCode;
+    @KeepaliveStatusCode
+    public final int statusCode;
 
     /** An error code indicating a lower layer failure, if any */
     public final int errorCode;
@@ -98,7 +99,7 @@
     }
 
     public static final Parcelable.Creator<KeepaliveStatus> CREATOR =
-            new Parcelable.Creator<KeepaliveStatus>() {
+            new Parcelable.Creator<>() {
                 @Override
                 public KeepaliveStatus createFromParcel(Parcel source) {
                     return new KeepaliveStatus(source);
diff --git a/src/java/com/android/internal/telephony/data/KeepaliveTracker.java b/src/java/com/android/internal/telephony/data/KeepaliveTracker.java
index f9139ec..f221779 100644
--- a/src/java/com/android/internal/telephony/data/KeepaliveTracker.java
+++ b/src/java/com/android/internal/telephony/data/KeepaliveTracker.java
@@ -56,19 +56,24 @@
     private static final int EVENT_UNREGISTER_FOR_KEEPALIVE_STATUS = 5;
 
     /** The phone instance. */
-    private final @NonNull Phone mPhone;
+    @NonNull
+    private final Phone mPhone;
 
     /** The parent data network. */
-    private final @NonNull DataNetwork mDataNetwork;
+    @NonNull
+    private final DataNetwork mDataNetwork;
 
     /** The associated network agent. */
-    private final @NonNull TelephonyNetworkAgent mNetworkAgent;
+    @NonNull
+    private final TelephonyNetworkAgent mNetworkAgent;
 
     /** The log tag. */
-    private final @NonNull String mLogTag;
+    @NonNull
+    private final String mLogTag;
 
     /** The keepalive records. */
-    private final @NonNull SparseArray<KeepaliveRecord> mKeepalives = new SparseArray<>();
+    @NonNull
+    private final SparseArray<KeepaliveRecord> mKeepalives = new SparseArray<>();
 
     /**
      * Keepalive session record
@@ -78,7 +83,8 @@
         public int slotIndex;
 
         /** The current status. */
-        public @KeepaliveStatusCode int currentStatus;
+        @KeepaliveStatusCode
+        public int currentStatus;
 
         /**
          * Constructor
@@ -254,17 +260,12 @@
      * @return The socket alive error.
      */
     private int keepaliveStatusErrorToPacketKeepaliveError(int error) {
-        switch(error) {
-            case KeepaliveStatus.ERROR_NONE:
-                return SocketKeepalive.SUCCESS;
-            case KeepaliveStatus.ERROR_UNSUPPORTED:
-                return SocketKeepalive.ERROR_UNSUPPORTED;
-            case KeepaliveStatus.ERROR_NO_RESOURCES:
-                return SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES;
-            case KeepaliveStatus.ERROR_UNKNOWN:
-            default:
-                return SocketKeepalive.ERROR_HARDWARE_ERROR;
-        }
+        return switch (error) {
+            case KeepaliveStatus.ERROR_NONE -> SocketKeepalive.SUCCESS;
+            case KeepaliveStatus.ERROR_UNSUPPORTED -> SocketKeepalive.ERROR_UNSUPPORTED;
+            case KeepaliveStatus.ERROR_NO_RESOURCES -> SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES;
+            default -> SocketKeepalive.ERROR_HARDWARE_ERROR;
+        };
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java b/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java
index de8c48c..534f191 100644
--- a/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java
+++ b/src/java/com/android/internal/telephony/data/LinkBandwidthEstimator.java
@@ -30,6 +30,7 @@
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.HandlerExecutor;
+import android.os.Looper;
 import android.os.Message;
 import android.os.OutcomeReceiver;
 import android.preference.PreferenceManager;
@@ -165,7 +166,6 @@
     private final Phone mPhone;
     private final TelephonyFacade mTelephonyFacade;
     private final TelephonyManager mTelephonyManager;
-    private final ConnectivityManager mConnectivityManager;
     private final LocalLog mLocalLog = new LocalLog(512);
     private boolean mScreenOn = false;
     private boolean mIsOnDefaultRoute = false;
@@ -185,22 +185,23 @@
     private int mTac;
     @NonNull private String mPlmn = UNKNOWN_PLMN;
     private NetworkCapabilities mNetworkCapabilities;
-    private NetworkBandwidth mPlaceholderNetwork;
+    private final NetworkBandwidth mPlaceholderNetwork;
     private long mFilterUpdateTimeMs;
 
     private int mBandwidthUpdateSignalDbm = -1;
     private int mBandwidthUpdateSignalLevel = -1;
     private int mBandwidthUpdateDataRat = TelephonyManager.NETWORK_TYPE_UNKNOWN;
     private String mBandwidthUpdatePlmn = UNKNOWN_PLMN;
-    private BandwidthState mTxState = new BandwidthState(LINK_TX);
-    private BandwidthState mRxState = new BandwidthState(LINK_RX);
+    private final BandwidthState mTxState = new BandwidthState(LINK_TX);
+    private final BandwidthState mRxState = new BandwidthState(LINK_RX);
     private long mLastPlmnOrRatChangeTimeMs;
     private long mLastDrsOrRatChangeTimeMs;
 
     private int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
 
     /** Link bandwidth estimator callbacks. */
-    private final @NonNull Set<LinkBandwidthEstimatorCallback> mLinkBandwidthEstimatorCallbacks =
+    @NonNull
+    private final Set<LinkBandwidthEstimatorCallback> mLinkBandwidthEstimatorCallbacks =
             new ArraySet<>();
 
     /**
@@ -270,14 +271,14 @@
 
     private final OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException>
             mOutcomeReceiver =
-            new OutcomeReceiver<ModemActivityInfo, TelephonyManager.ModemActivityInfoException>() {
+            new OutcomeReceiver<>() {
                 @Override
                 public void onResult(ModemActivityInfo result) {
                     obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, result).sendToTarget();
                 }
 
                 @Override
-                public void onError(TelephonyManager.ModemActivityInfoException e) {
+                public void onError(@NonNull TelephonyManager.ModemActivityInfoException e) {
                     Rlog.e(TAG, "error reading modem stats:" + e);
                     obtainMessage(MSG_MODEM_ACTIVITY_RETURNED, null).sendToTarget();
                 }
@@ -296,20 +297,26 @@
                 }
             };
 
-    public LinkBandwidthEstimator(Phone phone, TelephonyFacade telephonyFacade) {
+    public LinkBandwidthEstimator(Phone phone, Looper looper, TelephonyFacade telephonyFacade) {
+        super(looper);
         mPhone = phone;
         mTelephonyFacade = telephonyFacade;
         mTelephonyManager = phone.getContext()
                 .getSystemService(TelephonyManager.class)
                 .createForSubscriptionId(phone.getSubId());
-        mConnectivityManager = phone.getContext().getSystemService(ConnectivityManager.class);
         DisplayManager dm = (DisplayManager) phone.getContext().getSystemService(
                 Context.DISPLAY_SERVICE);
-        dm.registerDisplayListener(mDisplayListener, null);
+        if (dm != null) {
+            dm.registerDisplayListener(mDisplayListener, null);
+        }
         handleScreenStateChanged(isScreenOn());
-        mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
-        mTelephonyManager.registerTelephonyCallback(new HandlerExecutor(this),
-                mTelephonyCallback);
+
+        ConnectivityManager cm = phone.getContext()
+                .getSystemService(ConnectivityManager.class);
+        if (cm != null) {
+            cm.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
+        }
+        mTelephonyManager.registerTelephonyCallback(new HandlerExecutor(this), mTelephonyCallback);
         mPlaceholderNetwork = new NetworkBandwidth(UNKNOWN_PLMN);
         initAvgBwPerRatTable();
         registerNrStateFrequencyChange();
@@ -382,6 +389,7 @@
         // the screen is turned off transiently such as due to the proximity sensor.
         final DisplayManager dm = (DisplayManager) mPhone.getContext().getSystemService(
                 Context.DISPLAY_SERVICE);
+        if (dm == null) return false;
         Display[] displays = dm.getDisplays();
 
         if (displays != null) {
@@ -432,6 +440,7 @@
         handleTrafficStatsPollConditionChanged();
     }
 
+    @SuppressWarnings("unchecked")
     private void handleDrsOrRatChanged(AsyncResult ar) {
         Pair<Integer, Integer> drsRatPair = (Pair<Integer, Integer>) ar.result;
         logd("DrsOrRatChanged dataRegState " + drsRatPair.first + " rilRat " + drsRatPair.second);
@@ -729,7 +738,7 @@
                 return;
             }
             long filterOutKbps = (long) mFilterKbps * alpha
-                    + filterInKbps * FILTER_SCALE - filterInKbps * alpha;
+                    + (long) filterInKbps * FILTER_SCALE - (long) filterInKbps * alpha;
             filterOutKbps = filterOutKbps / FILTER_SCALE;
             mFilterKbps = (int) Math.min(filterOutKbps, Integer.MAX_VALUE);
 
@@ -877,8 +886,8 @@
 
             StringBuilder sb = new StringBuilder();
             logd(sb.append(mLink)
-                    .append(" sampKbps ").append(mBwSampleKbps)
-                    .append(" filtKbps ").append(mFilterKbps)
+                    .append(" sampleKbps ").append(mBwSampleKbps)
+                    .append(" filterKbps ").append(mFilterKbps)
                     .append(" reportKbps ").append(mLastReportedBwKbps)
                     .append(" avgUsedKbps ").append(mAvgUsedKbps)
                     .append(" csKbps ").append(mStaticBwKbps)
@@ -944,7 +953,8 @@
     /**
      * @return The data activity.
      */
-    public @DataActivityType int getDataActivity() {
+    @DataActivityType
+    public int getDataActivity() {
         return mDataActivity;
     }
 
@@ -1048,7 +1058,7 @@
         /* ss should always be non-null */
         if (!TextUtils.isEmpty(ss.getOperatorNumeric())) {
             plmn = ss.getOperatorNumeric();
-        } else if (cellIdentity != null && !TextUtils.isEmpty(cellIdentity.getPlmn())) {
+        } else if (!TextUtils.isEmpty(cellIdentity.getPlmn())) {
             plmn = cellIdentity.getPlmn();
         } else {
             plmn = UNKNOWN_PLMN;
@@ -1059,7 +1069,6 @@
             mPlmn = plmn;
         }
 
-        boolean updatedRat = false;
         NetworkRegistrationInfo nri = getDataNri();
         if (nri != null) {
             int dataRat = nri.getAccessNetworkTechnology();
@@ -1133,7 +1142,7 @@
         }
         @Override
         public boolean equals(@Nullable Object o) {
-            if (o == null || !(o instanceof NetworkKey) || hashCode() != o.hashCode()) {
+            if (!(o instanceof NetworkKey that) || hashCode() != o.hashCode()) {
                 return false;
             }
 
@@ -1141,7 +1150,6 @@
                 return true;
             }
 
-            NetworkKey that = (NetworkKey) o;
             return mPlmn.equals(that.mPlmn)
                     && mTac == that.mTac
                     && mDataRat.equals(that.mDataRat);
@@ -1153,11 +1161,7 @@
         }
         @Override
         public String toString() {
-            StringBuilder sb = new StringBuilder();
-            sb.append("Plmn").append(mPlmn)
-                    .append("Rat").append(mDataRat)
-                    .append("Tac").append(mTac);
-            return sb.toString();
+            return "Plmn" + mPlmn + "Rat" + mDataRat + "Tac" + mTac;
         }
     }
 
@@ -1216,11 +1220,7 @@
         }
 
         private String getDataKey(int link, int level) {
-            StringBuilder sb = new StringBuilder();
-            return sb.append(mKey)
-                    .append("Link").append(link)
-                    .append("Level").append(level)
-                    .toString();
+            return mKey + "Link" + link + "Level" + level;
         }
 
         /** Get the accumulated bandwidth value */
diff --git a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
index 8dc8098..27b4331 100644
--- a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony.data;
 
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.telephony.CarrierConfigManager.KEY_DATA_SWITCH_VALIDATION_TIMEOUT_LONG;
 import static android.telephony.SubscriptionManager.DEFAULT_PHONE_INDEX;
 import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
@@ -33,7 +32,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -47,7 +45,6 @@
 import android.net.NetworkSpecifier;
 import android.net.TelephonyNetworkSpecifier;
 import android.os.AsyncResult;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -56,7 +53,6 @@
 import android.os.RegistrantList;
 import android.os.RemoteException;
 import android.telephony.CarrierConfigManager;
-import android.telephony.PhoneCapability;
 import android.telephony.PhoneStateListener;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
@@ -69,6 +65,7 @@
 import android.util.ArrayMap;
 import android.util.LocalLog;
 import android.util.Log;
+import android.util.SparseIntArray;
 
 import com.android.ims.ImsException;
 import com.android.ims.ImsManager;
@@ -85,6 +82,7 @@
 import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
 import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback;
 import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyEvent.DataSwitch;
@@ -104,11 +102,12 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
 
 /**
  * Utility singleton to monitor subscription changes and incoming NetworkRequests
  * and determine which phone/phones are active.
- *
+ * <p>
  * Manages the ALLOW_DATA calls to modems and notifies phones about changes to
  * the active phones.  Note we don't wait for data attach (which may not happen anyway).
  */
@@ -183,10 +182,12 @@
         }
     }
 
-    private final @NonNull NetworkRequestList mNetworkRequestList = new NetworkRequestList();
+    @NonNull
+    private final NetworkRequestList mNetworkRequestList = new NetworkRequestList();
     protected final RegistrantList mActivePhoneRegistrants;
     private final SubscriptionManagerService mSubscriptionManagerService;
-    private final @NonNull FeatureFlags mFlags;
+    @NonNull
+    private final FeatureFlags mFlags;
     protected final Context mContext;
     private final LocalLog mLocalLog;
     protected PhoneState[] mPhoneStates;
@@ -215,7 +216,6 @@
                 }
             };
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     // How many phones (correspondingly logical modems) are allowed for PS attach. This is used
     // when we specifically use setDataAllowed to initiate on-demand PS(data) attach for each phone.
     protected int mMaxDataAttachModemCount;
@@ -311,7 +311,7 @@
     private static final int MAX_LOCAL_LOG_LINES = 256;
 
     // Default timeout value of network validation in millisecond.
-    private final static int DEFAULT_VALIDATION_EXPIRATION_TIME = 2000;
+    private static final int DEFAULT_VALIDATION_EXPIRATION_TIME = 2000;
 
     /** Controller that tracks {@link TelephonyManager#MOBILE_DATA_POLICY_AUTO_DATA_SWITCH} */
     @NonNull private final AutoDataSwitchController mAutoDataSwitchController;
@@ -319,22 +319,25 @@
     @NonNull private final AutoDataSwitchController.AutoDataSwitchControllerCallback
             mAutoDataSwitchCallback;
 
-    private ConnectivityManager mConnectivityManager;
+    private final ConnectivityManager mConnectivityManager;
     private int mImsRegistrationTech = REGISTRATION_TECH_NONE;
-
-    private List<Set<CommandException.Error>> mCurrentDdsSwitchFailure;
+    @VisibleForTesting
+    public final SparseIntArray mImsRegistrationRadioTechMap = new SparseIntArray();
+    @NonNull
+    private final List<Set<CommandException.Error>> mCurrentDdsSwitchFailure;
 
     /** Data settings manager callback. Key is the phone id. */
-    private final @NonNull Map<Integer, DataSettingsManagerCallback> mDataSettingsManagerCallbacks =
+    @NonNull
+    private final Map<Integer, DataSettingsManagerCallback> mDataSettingsManagerCallbacks =
             new ArrayMap<>();
 
     private class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback {
         public int mExpectedSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
         public int mSwitchReason = TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN;
         @Override
-        public void onCapabilitiesChanged(Network network,
+        public void onCapabilitiesChanged(@NonNull Network network,
                 NetworkCapabilities networkCapabilities) {
-            if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+            if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
                 if (SubscriptionManager.isValidSubscriptionId(mExpectedSubId)
                         && mExpectedSubId == getSubIdFromNetworkSpecifier(
                         networkCapabilities.getNetworkSpecifier())) {
@@ -350,15 +353,15 @@
         }
 
         @Override
-        public void onLost(Network network) {
+        public void onLost(@NonNull Network network) {
             mAutoDataSwitchController.updateDefaultNetworkCapabilities(null);
         }
     }
 
-    private RegistrationManager.RegistrationCallback mRegistrationCallback =
+    private final RegistrationManager.RegistrationCallback mRegistrationCallback =
             new RegistrationManager.RegistrationCallback() {
         @Override
-        public void onRegistered(ImsRegistrationAttributes attributes) {
+        public void onRegistered(@NonNull ImsRegistrationAttributes attributes) {
             int imsRegistrationTech = attributes.getRegistrationTechnology();
             if (imsRegistrationTech != mImsRegistrationTech) {
                 mImsRegistrationTech = imsRegistrationTech;
@@ -367,7 +370,7 @@
         }
 
         @Override
-        public void onUnregistered(ImsReasonInfo info) {
+        public void onUnregistered(@NonNull ImsReasonInfo info) {
             if (mImsRegistrationTech != REGISTRATION_TECH_NONE) {
                 mImsRegistrationTech = REGISTRATION_TECH_NONE;
                 sendMessage(obtainMessage(EVENT_IMS_RADIO_TECH_CHANGED));
@@ -391,6 +394,21 @@
             (context, phoneId) -> ImsManager.getInstance(context, phoneId).getRegistrationTech();
 
     /**
+     * Interface to register RegistrationCallback. It's a wrapper of
+     * ImsManager#addRegistrationCallback, to make it mock-able in unittests.
+     */
+    public interface ImsRegisterCallback {
+        /** Set RegistrationCallback. */
+        void setCallback(Context context, int phoneId, RegistrationManager.RegistrationCallback cb,
+                Executor executor) throws ImsException;
+    }
+
+    @VisibleForTesting
+    public ImsRegisterCallback mImsRegisterCallback =
+            (context, phoneId, cb, executor)-> ImsManager.getInstance(context, phoneId)
+                    .addRegistrationCallback(cb, executor);
+
+    /**
      * Method to get singleton instance.
      */
     public static PhoneSwitcher getInstance() {
@@ -433,8 +451,7 @@
 
     private void registerForImsRadioTechChange(Context context, int phoneId) {
         try {
-            ImsManager.getInstance(context, phoneId).addRegistrationCallback(
-                    mRegistrationCallback, this::post);
+            mImsRegisterCallback.setCallback(context, phoneId, mRegistrationCallback, this::post);
             mIsRegisteredForImsRadioTechChange = true;
         } catch (ImsException imsException) {
             mIsRegisteredForImsRadioTechChange = false;
@@ -477,7 +494,7 @@
         mRadioConfig = RadioConfig.getInstance();
         mValidator = CellularNetworkValidator.getInstance();
 
-        mCurrentDdsSwitchFailure = new ArrayList<Set<CommandException.Error>>();
+        mCurrentDdsSwitchFailure = new ArrayList<>();
         IntentFilter filter = new IntentFilter();
         filter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
         mContext.registerReceiver(mSimStateIntentReceiver, filter);
@@ -495,6 +512,14 @@
                 if (phone.getImsPhone() != null) {
                     phone.getImsPhone().registerForPreciseCallStateChanged(
                             this, EVENT_PRECISE_CALL_STATE_CHANGED, null);
+                    if (mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
+                        // Initialize IMS registration tech
+                        mImsRegistrationRadioTechMap.put(phoneId, REGISTRATION_TECH_NONE);
+                        ((ImsPhone) phone.getImsPhone()).registerForImsRegistrationChanges(
+                                this, EVENT_IMS_RADIO_TECH_CHANGED, null);
+
+                        log("register handler to receive IMS registration : " + phoneId);
+                    }
                 }
                 mDataSettingsManagerCallbacks.computeIfAbsent(phoneId,
                         v -> new DataSettingsManagerCallback(this::post) {
@@ -512,9 +537,12 @@
                             }});
                 phone.getDataSettingsManager().registerCallback(
                         mDataSettingsManagerCallbacks.get(phoneId));
-                registerForImsRadioTechChange(context, phoneId);
+
+                if (!mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
+                    registerForImsRadioTechChange(context, phoneId);
+                }
             }
-            Set<CommandException.Error> ddsFailure = new HashSet<CommandException.Error>();
+            Set<CommandException.Error> ddsFailure = new HashSet<>();
             mCurrentDdsSwitchFailure.add(ddsFailure);
         }
 
@@ -569,7 +597,7 @@
         mConnectivityManager.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
 
         final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder()
-                .addTransportType(TRANSPORT_CELLULAR)
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
@@ -594,6 +622,15 @@
                 .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_5)
                 .setNetworkSpecifier(new MatchAllNetworkSpecifier());
 
+        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
@@ -612,7 +649,7 @@
         }
     };
 
-    private BroadcastReceiver mSimStateIntentReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mSimStateIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
@@ -713,7 +750,18 @@
             case EVENT_IMS_RADIO_TECH_CHANGED: {
                 // register for radio tech change to listen to radio tech handover in case previous
                 // attempt was not successful
-                registerForImsRadioTechChange();
+                if (!mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
+                    registerForImsRadioTechChange();
+                } else {
+                    if (msg.obj == null) {
+                        log("EVENT_IMS_RADIO_TECH_CHANGED but parameter is not available");
+                        break;
+                    }
+                    if (!onImsRadioTechChanged((AsyncResult) (msg.obj))) {
+                        break;
+                    }
+                }
+
                 // if voice call state changes or in voice call didn't change
                 // but RAT changes(e.g. Iwlan -> cross sim), reevaluate for data switch.
                 if (updatesIfPhoneInVoiceCallChanged() || isAnyVoiceCallActiveOnDevice()) {
@@ -725,7 +773,9 @@
             case EVENT_PRECISE_CALL_STATE_CHANGED: {
                 // register for radio tech change to listen to radio tech handover in case previous
                 // attempt was not successful
-                registerForImsRadioTechChange();
+                if (!mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
+                    registerForImsRadioTechChange();
+                }
 
                 // If the phoneId in voice call didn't change, do nothing.
                 if (!updatesIfPhoneInVoiceCallChanged()) {
@@ -821,10 +871,8 @@
                             mEmergencyOverride.mOverrideCompleteFuture.complete(false);
                         }
                     }
-                    mEmergencyOverride = req;
-                } else {
-                    mEmergencyOverride = req;
                 }
+                mEmergencyOverride = req;
 
                 logl("new emergency override - " + mEmergencyOverride);
                 // a new request has been created, remove any previous override complete scheduled.
@@ -877,6 +925,41 @@
         }
     }
 
+    /**
+     * Only provide service for the handler of PhoneSwitcher.
+     * @return true if the radio tech changed, otherwise false
+     */
+    private boolean onImsRadioTechChanged(@NonNull AsyncResult asyncResult) {
+        ImsPhone.ImsRegistrationRadioTechInfo imsRegistrationRadioTechInfo =
+                (ImsPhone.ImsRegistrationRadioTechInfo) asyncResult.result;
+        if (imsRegistrationRadioTechInfo == null
+                || imsRegistrationRadioTechInfo.phoneId() == INVALID_PHONE_INDEX
+                || imsRegistrationRadioTechInfo.imsRegistrationState()
+                == RegistrationManager.REGISTRATION_STATE_REGISTERING) {
+            // Ignore REGISTERING state, handle only REGISTERED and NOT_REGISTERED
+            log("onImsRadioTechChanged : result is not available");
+            return false;
+        }
+
+        int phoneId = imsRegistrationRadioTechInfo.phoneId();
+        int subId = SubscriptionManager.getSubscriptionId(phoneId);
+        int tech = imsRegistrationRadioTechInfo.imsRegistrationTech();
+        log("onImsRadioTechChanged phoneId : " + phoneId + " subId : " + subId + " old tech : "
+                + mImsRegistrationRadioTechMap.get(phoneId, REGISTRATION_TECH_NONE)
+                + " new tech : " + tech);
+
+        if (mImsRegistrationRadioTechMap.get(phoneId, REGISTRATION_TECH_NONE) == tech) {
+            // Registration tech not changed
+            return false;
+        }
+
+        mImsRegistrationRadioTechMap.put(phoneId, tech);
+
+        // Need to update the cached IMS registration tech but no need to do any of the
+        // following. When the SIM removed, REGISTRATION_STATE_NOT_REGISTERED is notified.
+        return subId != INVALID_SUBSCRIPTION_ID;
+    }
+
     private synchronized void onMultiSimConfigChanged(int activeModemCount) {
         // No change.
         if (mActiveModemCount == activeModemCount) return;
@@ -903,6 +986,14 @@
             if (phone.getImsPhone() != null) {
                 phone.getImsPhone().registerForPreciseCallStateChanged(
                         this, EVENT_PRECISE_CALL_STATE_CHANGED, null);
+                if (mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
+                    // Initialize IMS registration tech for new phoneId
+                    mImsRegistrationRadioTechMap.put(phoneId, REGISTRATION_TECH_NONE);
+                    ((ImsPhone) phone.getImsPhone()).registerForImsRegistrationChanges(
+                            this, EVENT_IMS_RADIO_TECH_CHANGED, null);
+
+                    log("register handler to receive IMS registration : " + phoneId);
+                }
             }
 
             mDataSettingsManagerCallbacks.computeIfAbsent(phone.getPhoneId(),
@@ -923,9 +1014,12 @@
             phone.getDataSettingsManager().registerCallback(
                     mDataSettingsManagerCallbacks.get(phone.getPhoneId()));
 
-            Set<CommandException.Error> ddsFailure = new HashSet<CommandException.Error>();
+            Set<CommandException.Error> ddsFailure = new HashSet<>();
             mCurrentDdsSwitchFailure.add(ddsFailure);
-            registerForImsRadioTechChange(mContext, phoneId);
+
+            if (!mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
+                registerForImsRadioTechChange(mContext, phoneId);
+            }
         }
 
         mAutoDataSwitchController.onMultiSimConfigChanged(activeModemCount);
@@ -969,7 +1063,7 @@
         }
 
         @Override
-        protected void needNetworkFor(NetworkRequest networkRequest) {
+        protected void needNetworkFor(@NonNull NetworkRequest networkRequest) {
             if (VDBG) log("needNetworkFor " + networkRequest);
             Message msg = mPhoneSwitcher.obtainMessage(EVENT_REQUEST_NETWORK);
             msg.obj = networkRequest;
@@ -977,7 +1071,7 @@
         }
 
         @Override
-        protected void releaseNetworkFor(NetworkRequest networkRequest) {
+        protected void releaseNetworkFor(@NonNull NetworkRequest networkRequest) {
             if (VDBG) log("releaseNetworkFor " + networkRequest);
             Message msg = mPhoneSwitcher.obtainMessage(EVENT_RELEASE_NETWORK);
             msg.obj = networkRequest;
@@ -987,7 +1081,7 @@
 
     private void onRequestNetwork(NetworkRequest networkRequest) {
         TelephonyNetworkRequest telephonyNetworkRequest = new TelephonyNetworkRequest(
-                networkRequest, PhoneFactory.getDefaultPhone());
+                networkRequest, PhoneFactory.getDefaultPhone(), mFlags);
         if (!mNetworkRequestList.contains(telephonyNetworkRequest)) {
             mNetworkRequestList.add(telephonyNetworkRequest);
             onEvaluate(REQUESTS_CHANGED, "netRequest");
@@ -996,7 +1090,7 @@
 
     private void onReleaseNetwork(NetworkRequest networkRequest) {
         TelephonyNetworkRequest telephonyNetworkRequest = new TelephonyNetworkRequest(
-                networkRequest, PhoneFactory.getDefaultPhone());
+                networkRequest, PhoneFactory.getDefaultPhone(), mFlags);
         if (mNetworkRequestList.remove(telephonyNetworkRequest)) {
             onEvaluate(REQUESTS_CHANGED, "netReleased");
             collectReleaseNetworkMetrics(networkRequest);
@@ -1054,7 +1148,7 @@
     protected static final boolean REQUESTS_UNCHANGED = false;
     /**
      * Re-evaluate things. Do nothing if nothing's changed.
-     *
+     * <p>
      * Otherwise, go through the requests in priority order adding their phone until we've added up
      * to the max allowed.  Then go through shutting down phones that aren't in the active phone
      * list. Finally, activate all phones in the active phone list.
@@ -1092,10 +1186,14 @@
                     mAutoSelectedDataSubId = DEFAULT_SUBSCRIPTION_ID;
                 }
                 mPhoneSubscriptions[i] = sub;
-                // Listen to IMS radio tech change for new sub
-                if (SubscriptionManager.isValidSubscriptionId(sub)) {
-                    registerForImsRadioTechChange(mContext, i);
+
+                if (!mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
+                    // Listen to IMS radio tech change for new sub
+                    if (SubscriptionManager.isValidSubscriptionId(sub)) {
+                        registerForImsRadioTechChange(mContext, i);
+                    }
                 }
+
                 diffDetected = true;
                 mAutoDataSwitchController.notifySubscriptionsMappingChanged();
             }
@@ -1144,13 +1242,11 @@
                 }
                 sendRilCommands(mPreferredDataPhoneId);
             } else {
-                List<Integer> newActivePhones = new ArrayList<Integer>();
+                List<Integer> newActivePhones = new ArrayList<>();
 
-                /**
-                 * If all phones can have PS attached, activate all.
-                 * Otherwise, choose to activate phones according to requests. And
-                 * if list is not full, add mPreferredDataPhoneId.
-                 */
+                // If all phones can have PS attached, activate all.
+                // Otherwise, choose to activate phones according to requests. And
+                // if list is not full, add mPreferredDataPhoneId.
                 if (mMaxDataAttachModemCount == mActiveModemCount) {
                     for (int i = 0; i < mMaxDataAttachModemCount; i++) {
                         newActivePhones.add(i);
@@ -1208,12 +1304,10 @@
         public long lastRequested = 0;
     }
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected void activate(int phoneId) {
         switchPhone(phoneId, true);
     }
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected void deactivate(int phoneId) {
         switchPhone(phoneId, false);
     }
@@ -1240,7 +1334,7 @@
 
     /**
      * Switch the Default data for the context of an outgoing emergency call.
-     *
+     * <p>
      * In some cases, we need to try to switch the Default Data subscription before placing the
      * emergency call on DSDS devices. This includes the following situation:
      * - The modem does not support processing GNSS SUPL requests on the non-default data
@@ -1286,16 +1380,6 @@
         }
     }
 
-    private void onPhoneCapabilityChangedInternal(PhoneCapability capability) {
-        int newMaxDataAttachModemCount = TelephonyManager.getDefault()
-                .getNumberOfModemsWithSimultaneousDataConnections();
-        if (mMaxDataAttachModemCount != newMaxDataAttachModemCount) {
-            mMaxDataAttachModemCount = newMaxDataAttachModemCount;
-            logl("Max active phones changed to " + mMaxDataAttachModemCount);
-            onEvaluate(REQUESTS_UNCHANGED, "phoneCfgChanged");
-        }
-    }
-
     private int phoneIdForRequest(TelephonyNetworkRequest networkRequest) {
         NetworkRequest netRequest = networkRequest.getNativeNetworkRequest();
         int subId = getSubIdFromNetworkSpecifier(netRequest.getNetworkSpecifier());
@@ -1484,10 +1568,6 @@
         r.notifyRegistrant();
     }
 
-    public void unregisterForActivePhoneSwitch(Handler h) {
-        mActivePhoneRegistrants.remove(h);
-    }
-
     /**
      * Set opportunistic data subscription. It's an indication to switch Internet data to this
      * subscription. It has to be an active subscription, and PhoneSwitcher will try to validate
@@ -1554,6 +1634,9 @@
         mPendingSwitchSubId = INVALID_SUBSCRIPTION_ID;
 
         if (subIdToValidate == mPreferredDataSubId.get()) {
+            if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+                mAutoSelectedDataSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+            }
             sendSetOpptCallbackHelper(callback, SET_OPPORTUNISTIC_SUB_SUCCESS);
             return;
         }
@@ -1673,7 +1756,7 @@
 
     /**
      * Notify PhoneSwitcher to try to switch data to an opportunistic subscription.
-     *
+     * <p>
      * Set opportunistic data subscription. It's an indication to switch Internet data to this
      * subscription. It has to be an active subscription, and PhoneSwitcher will try to validate
      * it first if needed. If subId is DEFAULT_SUBSCRIPTION_ID, it means we are un-setting
@@ -1724,7 +1807,6 @@
      * Log debug messages and also log into the local log.
      * @param l debug messages
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     protected void logl(String l) {
         log(l);
         mLocalLog.log(l);
@@ -1753,20 +1835,16 @@
      * @param reason The switch reason.
      * @return The switch reason in string format.
      */
-    private static @NonNull String switchReasonToString(int reason) {
-        switch(reason) {
-            case TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN:
-                return "UNKNOWN";
-            case TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL:
-                return "MANUAL";
-            case TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL:
-                return "IN_CALL";
-            case TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_CBRS:
-                return "CBRS";
-            case TelephonyEvent.DataSwitch.Reason.DATA_SWITCH_REASON_AUTO:
-                return "AUTO";
-            default: return "UNKNOWN(" + reason + ")";
-        }
+    @NonNull
+    private static String switchReasonToString(int reason) {
+        return switch (reason) {
+            case DataSwitch.Reason.DATA_SWITCH_REASON_UNKNOWN -> "UNKNOWN";
+            case DataSwitch.Reason.DATA_SWITCH_REASON_MANUAL -> "MANUAL";
+            case DataSwitch.Reason.DATA_SWITCH_REASON_IN_CALL -> "IN_CALL";
+            case DataSwitch.Reason.DATA_SWITCH_REASON_CBRS -> "CBRS";
+            case DataSwitch.Reason.DATA_SWITCH_REASON_AUTO -> "AUTO";
+            default -> "UNKNOWN(" + reason + ")";
+        };
     }
 
     /**
@@ -1775,16 +1853,14 @@
      * @param state The switching state.
      * @return The switching state in string format.
      */
-    private static @NonNull String switchStateToString(int state) {
-        switch(state) {
-            case TelephonyEvent.EventState.EVENT_STATE_UNKNOWN:
-                return "UNKNOWN";
-            case TelephonyEvent.EventState.EVENT_STATE_START:
-                return "START";
-            case TelephonyEvent.EventState.EVENT_STATE_END:
-                return "END";
-            default: return "UNKNOWN(" + state + ")";
-        }
+    @NonNull
+    private static String switchStateToString(int state) {
+        return switch (state) {
+            case TelephonyEvent.EventState.EVENT_STATE_UNKNOWN -> "UNKNOWN";
+            case TelephonyEvent.EventState.EVENT_STATE_START -> "START";
+            case TelephonyEvent.EventState.EVENT_STATE_END -> "END";
+            default -> "UNKNOWN(" + state + ")";
+        };
     }
 
     /**
@@ -1827,12 +1903,6 @@
         return mAutoSelectedDataSubId;
     }
 
-    // TODO (b/148396668): add an internal callback method to monitor phone capability change,
-    // and hook this call to that callback.
-    private void onPhoneCapabilityChanged(PhoneCapability capability) {
-        onPhoneCapabilityChangedInternal(capability);
-    }
-
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
         pw.println("PhoneSwitcher:");
diff --git a/src/java/com/android/internal/telephony/data/QosCallbackTracker.java b/src/java/com/android/internal/telephony/data/QosCallbackTracker.java
index ac04627..a1ac379 100644
--- a/src/java/com/android/internal/telephony/data/QosCallbackTracker.java
+++ b/src/java/com/android/internal/telephony/data/QosCallbackTracker.java
@@ -53,13 +53,18 @@
     private static final int DEDICATED_BEARER_EVENT_STATE_MODIFIED = 2;
     private static final int DEDICATED_BEARER_EVENT_STATE_DELETED = 3;
 
-    private final @NonNull String mLogTag;
-    private final @NonNull TelephonyNetworkAgent mNetworkAgent;
-    private final @NonNull Map<Integer, QosBearerSession> mQosBearerSessions;
-    private final @NonNull RcsStats mRcsStats;
+    @NonNull
+    private final String mLogTag;
+    @NonNull
+    private final TelephonyNetworkAgent mNetworkAgent;
+    @NonNull
+    private final Map<Integer, QosBearerSession> mQosBearerSessions;
+    @NonNull
+    private final RcsStats mRcsStats;
 
     // We perform an exact match on the address
-    private final @NonNull Map<Integer, IFilter> mCallbacksToFilter;
+    @NonNull
+    private final Map<Integer, IFilter> mCallbacksToFilter;
 
     private final int mPhoneId;
 
@@ -139,11 +144,6 @@
                                     }
                                 });
                     }
-
-                    @Override
-                    public void onQosCallbackUnregistered(int qosCallbackId) {
-
-                    }
                 });
     }
 
@@ -255,7 +255,6 @@
                         // The filter matches which means it was previously available, and now is
                         // lost
                         if (doFiltersMatch(existingSession, filter)) {
-                            bearerState = DEDICATED_BEARER_EVENT_STATE_DELETED;
                             sendSessionLost(callbackId, existingSession);
                             notifyMetricDedicatedBearerEvent(existingSession, bearerState, true);
                             sessionsReportedToMetric.add(sessionId);
@@ -281,13 +280,13 @@
         });
     }
 
-    private boolean doFiltersMatch(final @NonNull QosBearerSession qosBearerSession,
-            final @NonNull IFilter filter) {
+    private boolean doFiltersMatch(@NonNull final QosBearerSession qosBearerSession,
+                                   @NonNull final IFilter filter) {
         return getMatchingQosBearerFilter(qosBearerSession, filter) != null;
     }
 
-    private boolean matchesByLocalAddress(final @NonNull QosBearerFilter sessionFilter,
-            final @NonNull IFilter filter) {
+    private boolean matchesByLocalAddress(@NonNull final QosBearerFilter sessionFilter,
+                                          @NonNull final IFilter filter) {
         int portStart;
         int portEnd;
         if (sessionFilter.getLocalPortRange() == null) {
@@ -316,7 +315,7 @@
     }
 
     private boolean matchesByRemoteAddress(@NonNull QosBearerFilter sessionFilter,
-            final @NonNull IFilter filter) {
+                                           @NonNull final IFilter filter) {
         int portStart;
         int portEnd;
         boolean result = false;
@@ -346,8 +345,8 @@
     }
 
     private boolean matchesByProtocol(@NonNull QosBearerFilter sessionFilter,
-            final @NonNull IFilter filter, boolean hasMatchedFilter) {
-        boolean result = false;
+                                      @NonNull final IFilter filter, boolean hasMatchedFilter) {
+        boolean result;
         int protocol = sessionFilter.getProtocol();
         if (protocol == QosBearerFilter.QOS_PROTOCOL_TCP
                 || protocol == QosBearerFilter.QOS_PROTOCOL_UDP) {
@@ -367,8 +366,9 @@
                 ? sessionFilter : qosFilter;
     }
 
-    private @Nullable QosBearerFilter getMatchingQosBearerFilter(
-            @NonNull QosBearerSession qosBearerSession, final @NonNull IFilter filter) {
+    @Nullable
+    private QosBearerFilter getMatchingQosBearerFilter(
+            @NonNull QosBearerSession qosBearerSession, @NonNull final IFilter filter) {
         QosBearerFilter qosFilter = null;
 
         for (final QosBearerFilter sessionFilter : qosBearerSession.getQosBearerFilterList()) {
@@ -406,11 +406,11 @@
         return qosFilter;
     }
 
-    private void sendSessionAvailable(final int callbackId, final @NonNull QosBearerSession session,
-            @NonNull IFilter filter) {
+    private void sendSessionAvailable(final int callbackId, @NonNull final QosBearerSession session,
+                                      @NonNull IFilter filter) {
         QosBearerFilter qosBearerFilter = getMatchingQosBearerFilter(session, filter);
         List<InetSocketAddress> remoteAddresses = new ArrayList<>();
-        if (qosBearerFilter.getRemoteAddresses().size() > 0
+        if (qosBearerFilter != null && !qosBearerFilter.getRemoteAddresses().isEmpty()
                 && qosBearerFilter.getRemotePortRange() != null) {
             remoteAddresses.add(
                     new InetSocketAddress(qosBearerFilter.getRemoteAddresses().get(0).getAddress(),
@@ -455,17 +455,16 @@
     }
 
     private void notifyMetricDedicatedBearerListenerAdded(final int callbackId,
-            final @NonNull QosBearerSession session) {
+                                                          @NonNull final QosBearerSession session) {
 
-        final int slotId = mPhoneId;
         final int rat = getRatInfoFromSessionInfo(session);
         final int qci = getQCIFromSessionInfo(session);
 
-        mRcsStats.onImsDedicatedBearerListenerAdded(callbackId, slotId, rat, qci);
+        mRcsStats.onImsDedicatedBearerListenerAdded(callbackId, mPhoneId, rat, qci);
     }
 
     private void notifyMetricDedicatedBearerListenerBearerUpdateSession(
-            final int callbackId, final @NonNull QosBearerSession session) {
+            final int callbackId, @NonNull final QosBearerSession session) {
         mRcsStats.onImsDedicatedBearerListenerUpdateSession(callbackId, mPhoneId,
                 getRatInfoFromSessionInfo(session), getQCIFromSessionInfo(session), true);
     }
@@ -518,13 +517,12 @@
 
     private void notifyMetricDedicatedBearerEvent(final QosBearerSession session,
             final int bearerState, final boolean hasListener) {
-        final int slotId = mPhoneId;
         int ratAtEnd = getRatInfoFromSessionInfo(session);
         int qci = getQCIFromSessionInfo(session);
         boolean localConnectionInfoReceived = doesLocalConnectionInfoExist(session);
         boolean remoteConnectionInfoReceived = doesRemoteConnectionInfoExist(session);
 
-        mRcsStats.onImsDedicatedBearerEvent(slotId, ratAtEnd, qci, bearerState,
+        mRcsStats.onImsDedicatedBearerEvent(mPhoneId, ratAtEnd, qci, bearerState,
                 localConnectionInfoReceived, remoteConnectionInfoReceived, hasListener);
     }
 
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkAgent.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkAgent.java
index 5ed09e6..5a81f19 100644
--- a/src/java/com/android/internal/telephony/data/TelephonyNetworkAgent.java
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkAgent.java
@@ -54,10 +54,8 @@
     private static final int NETWORK_AGENT_TEARDOWN_DELAY_MS = 5_000;
 
     /** The parent data network. */
-    private final @NonNull DataNetwork mDataNetwork;
-
-    /** Network agent config. For unit test use only. */
-    private final @NonNull NetworkAgentConfig mNetworkAgentConfig;
+    @NonNull
+    private final DataNetwork mDataNetwork;
 
     /** This is the id from {@link NetworkAgent#register()}. */
     private final int mId;
@@ -72,7 +70,8 @@
      * The callbacks that are used to pass information to {@link DataNetwork} and
      * {@link QosCallbackTracker}.
      */
-    private final @NonNull Set<TelephonyNetworkAgentCallback> mTelephonyNetworkAgentCallbacks =
+    @NonNull
+    private final Set<TelephonyNetworkAgentCallback> mTelephonyNetworkAgentCallbacks =
             new ArraySet<>();
 
     /**
@@ -112,7 +111,7 @@
 
         /**
          * Called when a qos callback is registered with a filter.
-         *
+         * <p>
          * Any QoS events that are sent with the same callback id after this method is called are a
          * no-op.
          *
@@ -165,7 +164,6 @@
                 config, provider);
         register();
         mDataNetwork = dataNetwork;
-        mNetworkAgentConfig = config;
         mTelephonyNetworkAgentCallbacks.add(callback);
         mId = getNetwork().getNetId();
         mLogTag = "TNA-" + mId;
@@ -270,7 +268,7 @@
      * @param filter the filter being registered
      */
     @Override
-    public void onQosCallbackRegistered(final int qosCallbackId, final @NonNull QosFilter filter) {
+    public void onQosCallbackRegistered(final int qosCallbackId, @NonNull final QosFilter filter) {
         if (mAbandoned) {
             log("The agent is already abandoned. Ignored onQosCallbackRegistered.");
             return;
@@ -281,7 +279,7 @@
 
     /**
      * Called when a qos callback is registered with a filter.
-     *
+     * <p>
      * Any QoS events that are sent with the same callback id after this method is called are a
      * no-op.
      *
@@ -345,15 +343,6 @@
     }
 
     /**
-     * Log debug messages and also log into the local log.
-     * @param s debug messages
-     */
-    private void logl(@NonNull String s) {
-        log(s);
-        mLocalLog.log(s);
-    }
-
-    /**
      * Dump the state of TelephonyNetworkAgent
      *
      * @param fd File descriptor
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java
index 377c219..ca34ca7 100644
--- a/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkFactory.java
@@ -72,14 +72,15 @@
 
     private final Phone mPhone;
 
-    private AccessNetworksManager mAccessNetworksManager;
+    private final AccessNetworksManager mAccessNetworksManager;
 
     private int mSubscriptionId;
 
     @VisibleForTesting
     public final Handler mInternalHandler;
 
-    private final @NonNull FeatureFlags mFlags;
+    @NonNull
+    private final FeatureFlags mFlags;
 
 
     /**
@@ -109,20 +110,19 @@
                 null);
 
         mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-        SubscriptionManager.from(mPhone.getContext()).addOnSubscriptionsChangedListener(
-                mSubscriptionsChangedListener);
+        SubscriptionManager.OnSubscriptionsChangedListener subscriptionsChangedListener =
+                new SubscriptionManager.OnSubscriptionsChangedListener() {
+                    @Override
+                    public void onSubscriptionsChanged() {
+                        mInternalHandler.sendEmptyMessage(EVENT_SUBSCRIPTION_CHANGED);
+                    }};
+
+        mPhone.getContext().getSystemService(SubscriptionManager.class)
+                .addOnSubscriptionsChangedListener(subscriptionsChangedListener);
 
         register();
     }
 
-    private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener =
-            new SubscriptionManager.OnSubscriptionsChangedListener() {
-                @Override
-                public void onSubscriptionsChanged() {
-                    mInternalHandler.sendEmptyMessage(EVENT_SUBSCRIPTION_CHANGED);
-                }
-            };
-
     private NetworkCapabilities makeNetworkFilterByPhoneId(int phoneId) {
         return makeNetworkFilter(SubscriptionManager.getSubscriptionId(phoneId));
     }
@@ -163,6 +163,16 @@
                 .addEnterpriseId(NetworkCapabilities.NET_ENTERPRISE_ID_5)
                 .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
                 .setSubscriptionId(subscriptionId).build());
+
+        if (mFlags.satelliteInternet()) {
+            // 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();
     }
 
@@ -255,15 +265,15 @@
     }
 
     @Override
-    public void needNetworkFor(NetworkRequest networkRequest) {
+    public void needNetworkFor(@NonNull NetworkRequest networkRequest) {
         Message msg = mInternalHandler.obtainMessage(EVENT_NETWORK_REQUEST);
         msg.obj = networkRequest;
         msg.sendToTarget();
     }
 
-    private void onNeedNetworkFor(Message msg) {
+    private void onNeedNetworkFor(@NonNull Message msg) {
         TelephonyNetworkRequest networkRequest =
-                new TelephonyNetworkRequest((NetworkRequest) msg.obj, mPhone);
+                new TelephonyNetworkRequest((NetworkRequest) msg.obj, mPhone, mFlags);
         boolean shouldApply = mPhoneSwitcher.shouldApplyNetworkRequest(
                 networkRequest, mPhone.getPhoneId());
 
@@ -281,15 +291,15 @@
     }
 
     @Override
-    public void releaseNetworkFor(NetworkRequest networkRequest) {
+    public void releaseNetworkFor(@NonNull NetworkRequest networkRequest) {
         Message msg = mInternalHandler.obtainMessage(EVENT_NETWORK_RELEASE);
         msg.obj = networkRequest;
         msg.sendToTarget();
     }
 
-    private void onReleaseNetworkFor(Message msg) {
+    private void onReleaseNetworkFor(@NonNull Message msg) {
         TelephonyNetworkRequest networkRequest =
-                new TelephonyNetworkRequest((NetworkRequest) msg.obj, mPhone);
+                new TelephonyNetworkRequest((NetworkRequest) msg.obj, mPhone, mFlags);
         boolean applied = mNetworkRequests.get(networkRequest)
                 != AccessNetworkConstants.TRANSPORT_TYPE_INVALID;
 
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
index 2668302..117eb36 100644
--- a/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
+++ b/src/java/com/android/internal/telephony/data/TelephonyNetworkRequest.java
@@ -24,6 +24,7 @@
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
 import android.os.SystemClock;
+import android.telephony.Annotation.ConnectivityTransport;
 import android.telephony.Annotation.NetCapability;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataProfile;
@@ -31,6 +32,7 @@
 import android.telephony.data.TrafficDescriptor.OsAppId;
 
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -136,19 +138,22 @@
     );
 
     /** The phone instance. */
-    private final @NonNull Phone mPhone;
+    @NonNull
+    private final Phone mPhone;
 
     /**
      * Native network request from the clients. See {@link NetworkRequest};
      */
-    private final @NonNull NetworkRequest mNativeNetworkRequest;
+    @NonNull
+    private final NetworkRequest mNativeNetworkRequest;
 
     /**
      * The attributes of the network capabilities in this network request. This describes how this
      * network request can be translated to different fields in {@link DataProfile} or perform
      * special actions in telephony.
      */
-    private final @NetCapabilityAttribute int mCapabilitiesAttributes;
+    @NetCapabilityAttribute
+    private final int mCapabilitiesAttributes;
 
     /**
      * Priority of the network request. The network request has higher priority will be satisfied
@@ -159,13 +164,15 @@
     /**
      * Data config manager for retrieving data config.
      */
-    private final @NonNull DataConfigManager mDataConfigManager;
+    @NonNull
+    private final DataConfigManager mDataConfigManager;
 
     /**
      * The attached data network. Note that the data network could be in any state. {@code null}
      * indicates this network request is not satisfied.
      */
-    private @Nullable DataNetwork mAttachedDataNetwork;
+    @Nullable
+    private DataNetwork mAttachedDataNetwork;
 
     /**
      * The state of the network request.
@@ -174,23 +181,33 @@
      * @see #REQUEST_STATE_SATISFIED
      */
     // This is not a boolean because there might be more states in the future.
-    private @RequestState int mState;
+    @RequestState
+    private int mState;
 
     /** The timestamp when this network request enters telephony. */
-    private final @ElapsedRealtimeLong long mCreatedTimeMillis;
+    @ElapsedRealtimeLong
+    private final long mCreatedTimeMillis;
 
     /** The data evaluation result. */
-    private @Nullable DataEvaluation mEvaluation;
+    @Nullable
+    private DataEvaluation mEvaluation;
+
+    /** Feature flag. */
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
 
     /**
      * Constructor
      *
      * @param request The native network request from the clients.
      * @param phone The phone instance
+     * @param featureFlags The feature flag
      */
-    public TelephonyNetworkRequest(NetworkRequest request, Phone phone) {
+    public TelephonyNetworkRequest(@NonNull NetworkRequest request, @NonNull Phone phone,
+                                   @NonNull FeatureFlags featureFlags) {
         mPhone = phone;
         mNativeNetworkRequest = request;
+        mFeatureFlags = featureFlags;
 
         int capabilitiesAttributes = CAPABILITY_ATTRIBUTE_NONE;
         for (int networkCapability : mNativeNetworkRequest.getCapabilities()) {
@@ -212,14 +229,17 @@
     /**
      * @see NetworkRequest#getNetworkSpecifier()
      */
-    public @Nullable NetworkSpecifier getNetworkSpecifier() {
+    @Nullable
+    public NetworkSpecifier getNetworkSpecifier() {
         return mNativeNetworkRequest.getNetworkSpecifier();
     }
 
     /**
      * @see NetworkRequest#getCapabilities()
      */
-    public @NonNull @NetCapability int[] getCapabilities() {
+    @NonNull
+    @NetCapability
+    public int[] getCapabilities() {
         return mNativeNetworkRequest.getCapabilities();
     }
 
@@ -231,6 +251,23 @@
     }
 
     /**
+     * @see NetworkRequest#getTransportTypes()
+     */
+    @NonNull
+    @ConnectivityTransport
+    public int[] getTransportTypes() {
+        return mNativeNetworkRequest.getTransportTypes();
+    }
+
+    /**
+     * @return {@code true} if the request can be served on the specified transport.
+     * @see NetworkRequest#hasTransport
+     */
+    public boolean hasTransport(@ConnectivityTransport int transport) {
+        return mNativeNetworkRequest.hasTransport(transport);
+    }
+
+    /**
      * @see NetworkRequest#canBeSatisfiedBy(NetworkCapabilities)
      */
     public boolean canBeSatisfiedBy(@Nullable NetworkCapabilities nc) {
@@ -274,6 +311,31 @@
         if ((hasAttribute(CAPABILITY_ATTRIBUTE_APN_SETTING)
                 || hasAttribute(CAPABILITY_ATTRIBUTE_TRAFFIC_DESCRIPTOR_DNN))
                 && dataProfile.getApnSetting() != null) {
+            if (mFeatureFlags.satelliteInternet()) {
+                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;
+                        }
+                    }
+                } else if (mNativeNetworkRequest.hasTransport(
+                        NetworkCapabilities.TRANSPORT_SATELLITE)
+                        && !mNativeNetworkRequest.hasTransport(
+                                NetworkCapabilities.TRANSPORT_CELLULAR)) {
+                    // If the request is explicitly for the satellite, then the data profile needs
+                    // to support satellite.
+                    if (!dataProfile.getApnSetting().isForInfrastructure(
+                            ApnSetting.INFRASTRUCTURE_SATELLITE)) {
+                        return false;
+                    }
+                }
+            }
             // Fallback to the legacy APN type matching.
             List<Integer> apnTypes = Arrays.stream(getCapabilities()).boxed()
                     .map(DataUtils::networkCapabilityToApnType)
@@ -322,7 +384,8 @@
      * @return The highest priority APN type based network capability from this network request. -1
      * if there is no APN type capabilities in this network request.
      */
-    public @NetCapability int getApnTypeNetworkCapability() {
+    @NetCapability
+    public int getApnTypeNetworkCapability() {
         if (!hasAttribute(CAPABILITY_ATTRIBUTE_APN_SETTING)) return -1;
         return Arrays.stream(getCapabilities()).boxed()
                 .filter(cap -> DataUtils.networkCapabilityToApnType(cap) != ApnSetting.TYPE_NONE)
@@ -332,7 +395,8 @@
     /**
      * @return The native network request.
      */
-    public @NonNull NetworkRequest getNativeNetworkRequest() {
+    @NonNull
+    public NetworkRequest getNativeNetworkRequest() {
         return mNativeNetworkRequest;
     }
 
@@ -341,7 +405,7 @@
      *
      * @param dataNetwork The data network.
      */
-    public void setAttachedNetwork(@NonNull DataNetwork dataNetwork) {
+    public void setAttachedNetwork(@Nullable DataNetwork dataNetwork) {
         mAttachedDataNetwork = dataNetwork;
     }
 
@@ -349,7 +413,8 @@
      * @return The attached network. {@code null} indicates the request is not attached to any
      * network (i.e. the request is unsatisfied).
      */
-    public @Nullable DataNetwork getAttachedNetwork() {
+    @Nullable
+    public DataNetwork getAttachedNetwork() {
         return mAttachedDataNetwork;
     }
 
@@ -365,7 +430,8 @@
     /**
      * @return The state of the network request.
      */
-    public @RequestState int getState() {
+    @RequestState
+    public int getState() {
         return mState;
     }
 
@@ -408,7 +474,8 @@
      * @return Os/App id. {@code null} if the request does not have traffic descriptor based network
      * capabilities.
      */
-    public @Nullable OsAppId getOsAppId() {
+    @Nullable
+    public OsAppId getOsAppId() {
         if (!hasAttribute(CAPABILITY_ATTRIBUTE_TRAFFIC_DESCRIPTOR_OS_APP_ID)) return null;
 
         // We do not support multiple network capabilities translated to Os/App id at this time.
@@ -439,18 +506,19 @@
      * @param state The request state.
      * @return The request state in string format.
      */
-    private static @NonNull String requestStateToString(
+    @NonNull
+    private static String requestStateToString(
             @TelephonyNetworkRequest.RequestState int state) {
-        switch (state) {
-            case TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED: return "UNSATISFIED";
-            case TelephonyNetworkRequest.REQUEST_STATE_SATISFIED: return "SATISFIED";
-            default: return "UNKNOWN(" + state + ")";
-        }
+        return switch (state) {
+            case TelephonyNetworkRequest.REQUEST_STATE_UNSATISFIED -> "UNSATISFIED";
+            case TelephonyNetworkRequest.REQUEST_STATE_SATISFIED -> "SATISFIED";
+            default -> "UNKNOWN(" + state + ")";
+        };
     }
 
     @Override
     public String toString() {
-        return "[" + mNativeNetworkRequest.toString() + ", mPriority=" + mPriority
+        return "[" + mNativeNetworkRequest + ", mPriority=" + mPriority
                 + ", state=" + requestStateToString(mState)
                 + ", mAttachedDataNetwork=" + (mAttachedDataNetwork != null
                 ? mAttachedDataNetwork.name() : null) + ", isMetered=" + isMeteredRequest()
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
index e3eed00..fddeb06 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionConnection.java
@@ -35,7 +35,9 @@
 import android.telephony.DomainSelector;
 import android.telephony.EmergencyRegistrationResult;
 import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PreciseDisconnectCause;
 import android.telephony.data.ApnSetting;
+import android.telephony.ims.ImsReasonInfo;
 import android.util.LocalLog;
 import android.util.Log;
 
@@ -351,6 +353,10 @@
 
     private boolean mIsTestMode = false;
 
+    private int mDisconnectCause = DisconnectCause.NOT_VALID;
+    private int mPreciseDisconnectCause = PreciseDisconnectCause.NOT_VALID;
+    private String mReasonMessage = null;
+
     /**
      * Creates an instance.
      *
@@ -789,6 +795,51 @@
     }
 
     /**
+     * Save call disconnect info for error propagation.
+     * @param disconnectCause The code for the reason for the disconnect.
+     * @param preciseDisconnectCause The code for the precise reason for the disconnect.
+     * @param reasonMessage Description of the reason for the disconnect, not intended for the user
+     *                      to see.
+     */
+    public void setDisconnectCause(int disconnectCause, int preciseDisconnectCause,
+                                String reasonMessage) {
+        mDisconnectCause = disconnectCause;
+        mPreciseDisconnectCause = preciseDisconnectCause;
+        mReasonMessage = reasonMessage;
+    }
+
+    public int getDisconnectCause() {
+        return mDisconnectCause;
+    }
+
+    public int getPreciseDisconnectCause() {
+        return mPreciseDisconnectCause;
+    }
+
+    public String getReasonMessage() {
+        return mReasonMessage;
+    }
+
+    /**
+     * @return imsReasonInfo Reason for the IMS call failure.
+     */
+    public @Nullable ImsReasonInfo getImsReasonInfo() {
+        if (getSelectionAttributes() == null) {
+            // Neither selectDomain(...) nor reselectDomain(...) has been called yet.
+            return null;
+        }
+
+        return getSelectionAttributes().getPsDisconnectCause();
+    }
+
+    /**
+     * @return phoneId To support localized message based on phoneId
+     */
+    public int getPhoneId() {
+        return getPhone().getPhoneId();
+    }
+
+    /**
      * Dumps local log.
      */
     public void dump(@NonNull PrintWriter printWriter) {
diff --git a/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java b/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
index ee8517d..e4ae592 100644
--- a/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
+++ b/src/java/com/android/internal/telephony/domainselection/DomainSelectionController.java
@@ -271,7 +271,9 @@
                 mHandler,
                 mRestartBindingRunnable);
 
-        int numPhones = TelephonyManager.getDefault().getActiveModemCount();
+        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+        int numPhones = tm.getSupportedModemCount();
+        logi("numPhones=" + numPhones);
         mConnectionCounts = new int[numPhones];
         for (int i = 0; i < numPhones; i++) {
             mConnectionCounts[i] = 0;
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/NormalCallDomainSelectionConnection.java b/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
index 0fd9201..095d61f 100644
--- a/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
+++ b/src/java/com/android/internal/telephony/domainselection/NormalCallDomainSelectionConnection.java
@@ -24,11 +24,9 @@
 import android.telecom.PhoneAccount;
 import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
 import android.telephony.Annotation.DisconnectCauses;
-import android.telephony.DisconnectCause;
 import android.telephony.DomainSelectionService;
 import android.telephony.DomainSelectionService.EmergencyScanType;
 import android.telephony.NetworkRegistrationInfo;
-import android.telephony.PreciseDisconnectCause;
 import android.telephony.ims.ImsReasonInfo;
 
 import com.android.internal.telephony.Phone;
@@ -41,9 +39,6 @@
 public class NormalCallDomainSelectionConnection extends DomainSelectionConnection {
 
     private static final boolean DBG = false;
-    private int mDisconnectCause = DisconnectCause.NOT_VALID;
-    private int mPreciseDisconnectCause = PreciseDisconnectCause.NOT_VALID;
-    private String mReasonMessage = null;
 
     private @Nullable DomainSelectionConnectionCallback mCallback;
 
@@ -130,49 +125,4 @@
         }
         return builder.build();
     }
-
-    /**
-     * Save call disconnect info for error propagation.
-     * @param disconnectCause The code for the reason for the disconnect.
-     * @param preciseDisconnectCause The code for the precise reason for the disconnect.
-     * @param reasonMessage Description of the reason for the disconnect, not intended for the user
-     *                      to see.
-     */
-    public void setDisconnectCause(int disconnectCause, int preciseDisconnectCause,
-                                String reasonMessage) {
-        mDisconnectCause = disconnectCause;
-        mPreciseDisconnectCause = preciseDisconnectCause;
-        mReasonMessage = reasonMessage;
-    }
-
-    public int getDisconnectCause() {
-        return mDisconnectCause;
-    }
-
-    public int getPreciseDisconnectCause() {
-        return mPreciseDisconnectCause;
-    }
-
-    public String getReasonMessage() {
-        return mReasonMessage;
-    }
-
-    /**
-     * @return imsReasonInfo Reason for the IMS call failure.
-     */
-    public @Nullable ImsReasonInfo getImsReasonInfo() {
-        if (getSelectionAttributes() == null) {
-            // Neither selectDomain(...) nor reselectDomain(...) has been called yet.
-            return null;
-        }
-
-        return getSelectionAttributes().getPsDisconnectCause();
-    }
-
-    /**
-     * @return phoneId To support localized message based on phoneId
-     */
-    public int getPhoneId() {
-        return getPhone().getPhoneId();
-    }
 }
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
index 02dd613..06ebff2 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyNumberTracker.java
@@ -50,6 +50,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.TelephonyCapabilities;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.EmergencyNumberStats;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
@@ -186,7 +187,7 @@
         mFeatureFlags = featureFlags;
         mResources = ctx.getResources();
 
-        if (mFeatureFlags.minimalTelephonyCdmCheck()
+        if (TelephonyCapabilities.minimalTelephonyCdmCheck(mFeatureFlags)
                 && !ctx.getPackageManager().hasSystemFeature(
                     PackageManager.FEATURE_TELEPHONY_CALLING)) {
             throw new UnsupportedOperationException("EmergencyNumberTracker requires calling");
diff --git a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
index c4d5355..8478cff 100644
--- a/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
+++ b/src/java/com/android/internal/telephony/emergency/EmergencyStateTracker.java
@@ -16,8 +16,12 @@
 
 package com.android.internal.telephony.emergency;
 
+import static android.telecom.Connection.STATE_ACTIVE;
+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 com.android.internal.telephony.TelephonyIntents.ACTION_EMERGENCY_CALL_STATE_CHANGED;
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_CALLBACK;
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_NONE;
 import static com.android.internal.telephony.emergency.EmergencyConstants.MODE_EMERGENCY_WWAN;
@@ -53,12 +57,15 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.GsmCdmaPhone;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.data.PhoneSwitcher;
+import com.android.internal.telephony.imsphone.ImsPhoneConnection;
 import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.telephony.Rlog;
 
@@ -77,6 +84,19 @@
 
     private static final String TAG = "EmergencyStateTracker";
 
+    private static class OnDisconnectListener extends Connection.ListenerBase {
+        private final CompletableFuture<Boolean> mFuture;
+
+        OnDisconnectListener(CompletableFuture<Boolean> future) {
+            mFuture = future;
+        }
+
+        @Override
+        public void onDisconnect(int cause) {
+            mFuture.complete(true);
+        }
+    };
+
     /**
      * Timeout before we continue with the emergency call without waiting for DDS switch response
      * from the modem.
@@ -91,6 +111,9 @@
 
     private static final int DEFAULT_TRANSPORT_CHANGE_TIMEOUT_MS = 1 * 1000;
 
+    // Timeout to wait for the termination of incoming call before continue with the emergency call.
+    private static final int DEFAULT_REJECT_INCOMING_CALL_TIMEOUT_MS = 3 * 1000; // 3 seconds.
+
     /** The emergency types used when setting the emergency mode on modem. */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "EMERGENCY_TYPE_",
@@ -120,6 +143,7 @@
     private EmergencyRegistrationResult mLastEmergencyRegistrationResult;
     private boolean mIsEmergencyModeInProgress;
     private boolean mIsEmergencyCallStartedDuringEmergencySms;
+    private boolean mIsWaitingForRadioOff;
 
     /** For emergency calls */
     private final long mEcmExitTimeoutMs;
@@ -138,6 +162,8 @@
     private boolean mIsTestEmergencyNumber;
     private Runnable mOnEcmExitCompleteRunnable;
     private int mOngoingCallProperties;
+    private boolean mSentEmergencyCallState;
+    private android.telecom.Connection mNormalRoutingEmergencyConnection;
 
     /** For emergency SMS */
     private final Set<String> mOngoingEmergencySmsIds = new ArraySet<>();
@@ -149,9 +175,12 @@
     private boolean mIsEmergencySmsStartedDuringScbm;
 
     private CompletableFuture<Boolean> mEmergencyTransportChangedFuture;
+    private final Object mRegistrantidentifier = new Object();
 
     private final android.util.ArrayMap<Integer, Boolean> mNoSimEcbmSupported =
             new android.util.ArrayMap<>();
+    private final android.util.ArrayMap<Integer, Boolean> mBroadcastEmergencyCallStateChanges =
+            new android.util.ArrayMap<>();
     private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
             (slotIndex, subId, carrierId, specificCarrierId) -> onCarrierConfigurationChanged(
                     slotIndex, subId);
@@ -200,6 +229,7 @@
     @VisibleForTesting
     public interface TelephonyManagerProxy {
         int getPhoneCount();
+        int getSimState(int slotIndex);
     }
 
     private final TelephonyManagerProxy mTelephonyManagerProxy;
@@ -215,6 +245,11 @@
         public int getPhoneCount() {
             return mTelephonyManager.getActiveModemCount();
         }
+
+        @Override
+        public int getSimState(int slotIndex) {
+            return mTelephonyManager.getSimState(slotIndex);
+        }
     }
 
     /**
@@ -233,6 +268,10 @@
     public static final int MSG_SET_EMERGENCY_CALLBACK_MODE_DONE = 3;
     /** A message which is used to automatically exit from SCBM after a period of time. */
     private static final int MSG_EXIT_SCBM = 4;
+    @VisibleForTesting
+    public static final int MSG_NEW_RINGING_CONNECTION = 5;
+    @VisibleForTesting
+    public static final int MSG_VOICE_REG_STATE_CHANGED = 6;
 
     private class MyHandler extends Handler {
 
@@ -325,11 +364,26 @@
                             mOnEcmExitCompleteRunnable.run();
                             mOnEcmExitCompleteRunnable = null;
                         }
+                        if (mPhone != null && mEmergencyMode == MODE_EMERGENCY_WWAN) {
+                            // In cross sim redialing.
+                            setEmergencyModeInProgress(true);
+                            mWasEmergencyModeSetOnModem = true;
+                            mPhone.setEmergencyMode(MODE_EMERGENCY_WWAN,
+                                    mHandler.obtainMessage(MSG_SET_EMERGENCY_MODE_DONE,
+                                    Integer.valueOf(EMERGENCY_TYPE_CALL)));
+                        }
                     } else if (emergencyType == EMERGENCY_TYPE_SMS) {
                         if (mIsEmergencyCallStartedDuringEmergencySms) {
                             mIsEmergencyCallStartedDuringEmergencySms = false;
                             turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
                                     mIsTestEmergencyNumber);
+                        } else if (mPhone != null && mEmergencyMode == MODE_EMERGENCY_WWAN) {
+                            // Starting emergency call while exiting emergency mode
+                            setEmergencyModeInProgress(true);
+                            mWasEmergencyModeSetOnModem = true;
+                            mPhone.setEmergencyMode(MODE_EMERGENCY_WWAN,
+                                    mHandler.obtainMessage(MSG_SET_EMERGENCY_MODE_DONE,
+                                    Integer.valueOf(EMERGENCY_TYPE_CALL)));
                         } else if (mIsEmergencySmsStartedDuringScbm) {
                             mIsEmergencySmsStartedDuringScbm = false;
                             setEmergencyMode(mSmsPhone, emergencyType,
@@ -377,6 +431,20 @@
                     exitEmergencySmsCallbackModeAndEmergencyMode();
                     break;
                 }
+                case MSG_NEW_RINGING_CONNECTION: {
+                    handleNewRingingConnection(msg);
+                    break;
+                }
+                case MSG_VOICE_REG_STATE_CHANGED: {
+                    if (mIsWaitingForRadioOff && isPowerOff()) {
+                        unregisterForVoiceRegStateOrRatChanged();
+                        if (mPhone != null) {
+                            turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL,
+                                    mIsTestEmergencyNumber);
+                        }
+                    }
+                    break;
+                }
                 default:
                     break;
             }
@@ -436,6 +504,11 @@
         filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
         context.registerReceiver(mEcmExitReceiver, filter, null, mHandler);
         mTelephonyManagerProxy = new TelephonyManagerProxyImpl(context);
+
+        registerForNewRingingConnection();
+
+        // To recover the abnormal state after crash of com.android.phone process
+        maybeResetEmergencyCallStateChangedIntent();
     }
 
     /**
@@ -471,6 +544,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
         context.registerReceiver(mEcmExitReceiver, filter, null, mHandler);
+        registerForNewRingingConnection();
     }
 
     /**
@@ -550,6 +624,8 @@
                 mPhone = phone;
                 mOngoingConnection = c;
                 mIsTestEmergencyNumber = isTestEmergencyNumber;
+                sendEmergencyCallStateChange(mPhone, true);
+                maybeRejectIncomingCall(null);
                 return mCallEmergencyModeFuture;
             }
         }
@@ -557,7 +633,17 @@
         mPhone = phone;
         mOngoingConnection = c;
         mIsTestEmergencyNumber = isTestEmergencyNumber;
-        turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, mIsTestEmergencyNumber);
+        sendEmergencyCallStateChange(mPhone, true);
+        final android.telecom.Connection expectedConnection = mOngoingConnection;
+        maybeRejectIncomingCall(result -> {
+            Rlog.i(TAG, "maybeRejectIncomingCall : result = " + result);
+            if (!Objects.equals(mOngoingConnection, expectedConnection)) {
+                Rlog.i(TAG, "maybeRejectIncomingCall "
+                        + expectedConnection.getTelecomCallId() + " canceled.");
+                return;
+            }
+            turnOnRadioAndSwitchDds(mPhone, EMERGENCY_TYPE_CALL, mIsTestEmergencyNumber);
+        });
         return mCallEmergencyModeFuture;
     }
 
@@ -576,6 +662,8 @@
         if (Objects.equals(mOngoingConnection, c)) {
             mOngoingConnection = null;
             mOngoingCallProperties = 0;
+            sendEmergencyCallStateChange(mPhone, false);
+            unregisterForVoiceRegStateOrRatChanged();
         }
 
         if (wasActive && mActiveEmergencyCalls.isEmpty()
@@ -667,6 +755,16 @@
             maybeNotifyTransportChangeCompleted(emergencyType, false);
             return;
         }
+
+        if (emergencyType == EMERGENCY_TYPE_CALL
+                && mode == MODE_EMERGENCY_WWAN
+                && isEmergencyModeInProgress() && !isInEmergencyMode()) {
+            // In cross sim redialing or ending emergency SMS, exitEmergencyMode is not completed.
+            mEmergencyMode = mode;
+            Rlog.i(TAG, "setEmergencyMode wait for the completion of exitEmergencyMode");
+            return;
+        }
+
         mEmergencyMode = mode;
         setEmergencyModeInProgress(true);
 
@@ -1000,11 +1098,14 @@
      */
     @VisibleForTesting
     public boolean isEmergencyCallbackModeSupported(Phone phone) {
+        if (phone == null) {
+            return false;
+        }
         int subId = phone.getSubId();
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+        int phoneId = phone.getPhoneId();
+        if (!isSimReady(phoneId, subId)) {
             // If there is no SIM, refer to the saved last carrier configuration with valid
             // subscription.
-            int phoneId = phone.getPhoneId();
             Boolean savedConfig = mNoSimEcbmSupported.get(Integer.valueOf(phoneId));
             if (savedConfig == null) {
                 // Exceptional case such as with poor boot performance.
@@ -1165,6 +1266,27 @@
     }
 
     /**
+     * Returns {@code true} if currently in emergency callback mode with the given {@link Phone}.
+     *
+     * @param phone the {@link Phone} for the emergency call.
+     */
+    public boolean isInEcm(Phone phone) {
+        return isInEcm() && isSamePhone(mPhone, phone);
+    }
+
+    private void sendEmergencyCallStateChange(Phone phone, boolean isAlive) {
+        if ((isAlive && !mSentEmergencyCallState && getBroadcastEmergencyCallStateChanges(phone))
+                || (!isAlive && mSentEmergencyCallState)) {
+            mSentEmergencyCallState = isAlive;
+            Rlog.i(TAG, "sendEmergencyCallStateChange: " + isAlive);
+            Intent intent = new Intent(ACTION_EMERGENCY_CALL_STATE_CHANGED);
+            intent.putExtra(TelephonyManager.EXTRA_PHONE_IN_EMERGENCY_CALL, isAlive);
+            SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phone.getPhoneId());
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        }
+    }
+
+    /**
      * Starts the process of an emergency SMS.
      *
      * @param phone the {@code Phone} on which to process the emergency SMS.
@@ -1251,10 +1373,17 @@
      * @param success the flag specifying whether an emergency SMS is successfully sent or not.
      *                {@code true} if SMS is successfully sent, {@code false} otherwise.
      * @param domain the domain that MO SMS was sent.
+     * @param isLastSmsPart the flag specifying whether this result is for the last SMS part or not.
      */
     public void endSms(@NonNull String smsId, boolean success,
-            @NetworkRegistrationInfo.Domain int domain) {
-        mOngoingEmergencySmsIds.remove(smsId);
+            @NetworkRegistrationInfo.Domain int domain, boolean isLastSmsPart) {
+        if (success && !isLastSmsPart) {
+            // Waits until all SMS parts are sent successfully.
+            // Ensures that all SMS parts are sent while in the emergency mode.
+            Rlog.i(TAG, "endSms: wait for additional SMS parts to be sent.");
+        } else {
+            mOngoingEmergencySmsIds.remove(smsId);
+        }
 
         // If the outgoing emergency SMSs are empty, we can try to exit the emergency mode.
         if (mOngoingEmergencySmsIds.isEmpty()) {
@@ -1277,7 +1406,9 @@
                 // Sets the emergency mode to CALLBACK without re-initiating SCBM timer.
                 setEmergencyCallbackMode(mSmsPhone, EMERGENCY_TYPE_SMS);
             } else {
-                exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+                if (mSmsPhone != null) {
+                    exitEmergencyMode(mSmsPhone, EMERGENCY_TYPE_SMS);
+                }
                 clearEmergencySmsInfo();
             }
         }
@@ -1397,6 +1528,34 @@
     }
 
     /**
+     * Returns {@code true} if service states of all phones from PhoneFactory are radio off.
+     */
+    private boolean isPowerOff() {
+        for (Phone phone : mPhoneFactoryProxy.getPhones()) {
+            ServiceState ss = phone.getServiceStateTracker().getServiceState();
+            if (ss.getState() != ServiceState.STATE_POWER_OFF) return false;
+        }
+        return true;
+    }
+
+    private void registerForVoiceRegStateOrRatChanged() {
+        if (mIsWaitingForRadioOff) return;
+        for (Phone phone : mPhoneFactoryProxy.getPhones()) {
+            phone.getServiceStateTracker().registerForVoiceRegStateOrRatChanged(mHandler,
+                    MSG_VOICE_REG_STATE_CHANGED, null);
+        }
+        mIsWaitingForRadioOff = true;
+    }
+
+    private void unregisterForVoiceRegStateOrRatChanged() {
+        if (!mIsWaitingForRadioOff) return;
+        for (Phone phone : mPhoneFactoryProxy.getPhones()) {
+            phone.getServiceStateTracker().unregisterForVoiceRegStateOrRatChanged(mHandler);
+        }
+        mIsWaitingForRadioOff = false;
+    }
+
+    /**
      * Returns {@code true} if airplane mode is on.
      */
     private boolean isAirplaneModeOn(Context context) {
@@ -1426,6 +1585,14 @@
         final SatelliteController satelliteController = SatelliteController.getInstance();
         boolean needToTurnOffSatellite = satelliteController.isSatelliteEnabled();
 
+        if (isAirplaneModeOn && !isPowerOff()
+                && !phone.getServiceStateTracker().getDesiredPowerState()) {
+            // power off is delayed to disconnect data connections
+            Rlog.i(TAG, "turnOnRadioAndSwitchDds: wait for the delayed power off");
+            registerForVoiceRegStateOrRatChanged();
+            return;
+        }
+
         if (needToTurnOnRadio || needToTurnOffSatellite) {
             Rlog.i(TAG, "turnOnRadioAndSwitchDds: phoneId=" + phone.getPhoneId() + " for "
                     + emergencyTypeToString(emergencyType));
@@ -1463,6 +1630,11 @@
 
                 @Override
                 public boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable) {
+                    if (!Objects.equals(mOngoingConnection, expectedConnection)) {
+                        Rlog.i(TAG, "isOkToCall "
+                                + expectedConnection.getTelecomCallId() + " canceled.");
+                        return true;
+                    }
                     // Wait for normal service state or timeout if required.
                     if (phone == phoneForEmergency
                             && waitForInServiceTimeout > 0
@@ -1475,6 +1647,11 @@
 
                 @Override
                 public boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable) {
+                    if (!Objects.equals(mOngoingConnection, expectedConnection)) {
+                        Rlog.i(TAG, "onTimeout "
+                                + expectedConnection.getTelecomCallId() + " canceled.");
+                        return true;
+                    }
                     // onTimeout shall be called only with the Phone for emergency
                     return phone.getServiceStateTracker().isRadioOn()
                             && !satelliteController.isSatelliteEnabled();
@@ -1619,9 +1796,9 @@
     private boolean getConfig(int subId, String key, boolean defVal) {
         return getConfigBundle(subId, key).getBoolean(key, defVal);
     }
-    private PersistableBundle getConfigBundle(int subId, String key) {
+    private PersistableBundle getConfigBundle(int subId, String... keys) {
         if (mConfigManager == null) return new PersistableBundle();
-        return mConfigManager.getConfigForSubId(subId, key);
+        return mConfigManager.getConfigForSubId(subId, keys);
     }
 
     /**
@@ -1675,10 +1852,6 @@
             return;
         }
 
-        updateNoSimEcbmSupported(slotIndex, subId);
-    }
-
-    private void updateNoSimEcbmSupported(int slotIndex, int subId) {
         SharedPreferences sp = null;
         Boolean savedConfig = mNoSimEcbmSupported.get(Integer.valueOf(slotIndex));
         if (savedConfig == null) {
@@ -1686,26 +1859,37 @@
             savedConfig = Boolean.valueOf(
                     sp.getBoolean(KEY_NO_SIM_ECBM_SUPPORT + slotIndex, false));
             mNoSimEcbmSupported.put(Integer.valueOf(slotIndex), savedConfig);
-            Rlog.i(TAG, "updateNoSimEcbmSupported load from preference slotIndex=" + slotIndex
-                    + ", supported=" + savedConfig);
+            Rlog.i(TAG, "onCarrierConfigChanged load from preference slotIndex=" + slotIndex
+                    + ", ecbmSupported=" + savedConfig);
         }
 
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-            // invalid subId
+        if (!isSimReady(slotIndex, subId)) {
+            Rlog.i(TAG, "onCarrierConfigChanged SIM not ready");
             return;
         }
 
-        PersistableBundle b = getConfigBundle(subId, KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL);
+        PersistableBundle b = getConfigBundle(subId,
+                KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL,
+                KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL);
         if (b.isEmpty()) {
-            Rlog.e(TAG, "updateNoSimEcbmSupported empty result");
+            Rlog.e(TAG, "onCarrierConfigChanged empty result");
             return;
         }
 
         if (!CarrierConfigManager.isConfigForIdentifiedCarrier(b)) {
-            Rlog.i(TAG, "updateNoSimEcbmSupported not carrier specific configuration");
+            Rlog.i(TAG, "onCarrierConfigChanged not carrier specific configuration");
             return;
         }
 
+        // KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL
+        boolean broadcast = b.getBoolean(KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL);
+        mBroadcastEmergencyCallStateChanges.put(
+                Integer.valueOf(slotIndex), Boolean.valueOf(broadcast));
+
+        Rlog.i(TAG, "onCarrierConfigChanged slotIndex=" + slotIndex
+                + ", broadcastEmergencyCallStateChanges=" + broadcast);
+
+        // KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL
         boolean carrierConfig = b.getBoolean(KEY_EMERGENCY_CALLBACK_MODE_SUPPORTED_BOOL);
         if (carrierConfig == savedConfig) {
             return;
@@ -1720,7 +1904,205 @@
         editor.putBoolean(KEY_NO_SIM_ECBM_SUPPORT + slotIndex, carrierConfig);
         editor.apply();
 
-        Rlog.i(TAG, "updateNoSimEcbmSupported preference updated slotIndex=" + slotIndex
-                + ", supported=" + carrierConfig);
+        Rlog.i(TAG, "onCarrierConfigChanged preference updated slotIndex=" + slotIndex
+                + ", ecbmSupported=" + carrierConfig);
+    }
+
+    private boolean isSimReady(int slotIndex, int subId) {
+        return SubscriptionManager.isValidSubscriptionId(subId)
+                && mTelephonyManagerProxy.getSimState(slotIndex)
+                        == TelephonyManager.SIM_STATE_READY;
+    }
+
+    private boolean getBroadcastEmergencyCallStateChanges(Phone phone) {
+        Boolean broadcast = mBroadcastEmergencyCallStateChanges.get(
+                Integer.valueOf(phone.getPhoneId()));
+        return (broadcast == null) ? false : broadcast;
+    }
+
+    /**
+     * Resets the emergency call state if it's in alive state.
+     */
+    @VisibleForTesting
+    public void maybeResetEmergencyCallStateChangedIntent() {
+        Intent intent = mContext.registerReceiver(null,
+            new IntentFilter(ACTION_EMERGENCY_CALL_STATE_CHANGED), Context.RECEIVER_NOT_EXPORTED);
+        if (intent != null
+                && ACTION_EMERGENCY_CALL_STATE_CHANGED.equals(intent.getAction())) {
+            boolean isAlive = intent.getBooleanExtra(
+                    TelephonyManager.EXTRA_PHONE_IN_EMERGENCY_CALL, false);
+            Rlog.i(TAG, "maybeResetEmergencyCallStateChangedIntent isAlive=" + isAlive);
+            if (isAlive) {
+                intent = new Intent(ACTION_EMERGENCY_CALL_STATE_CHANGED);
+                intent.putExtra(TelephonyManager.EXTRA_PHONE_IN_EMERGENCY_CALL, false);
+                mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+            }
+        }
+    }
+
+    private Call getRingingCall(Phone phone) {
+        if (phone == null) return null;
+        Call ringingCall = phone.getRingingCall();
+        if (ringingCall != null
+                && ringingCall.getState() != Call.State.IDLE
+                && ringingCall.getState() != Call.State.DISCONNECTED) {
+            return ringingCall;
+        }
+        // Check the ImsPhoneCall in DISCONNECTING state.
+        Phone imsPhone = phone.getImsPhone();
+        if (imsPhone != null) {
+            ringingCall = imsPhone.getRingingCall();
+        }
+        if (imsPhone != null && ringingCall != null
+                && ringingCall.getState() != Call.State.IDLE
+                && ringingCall.getState() != Call.State.DISCONNECTED) {
+            return ringingCall;
+        }
+        return null;
+    }
+
+    /**
+     * Ensures that there is no incoming call.
+     *
+     * @param completeConsumer The consumer to call once rejecting incoming call completes,
+     *                         provides {@code true} result if operation completes successfully
+     *                         or {@code false} if the operation timed out/failed.
+     */
+    private void maybeRejectIncomingCall(Consumer<Boolean> completeConsumer) {
+        Phone[] phones = mPhoneFactoryProxy.getPhones();
+        if (phones == null) {
+            if (completeConsumer != null) {
+                completeConsumer.accept(true);
+            }
+            return;
+        }
+
+        Call ringingCall = null;
+        for (Phone phone : phones) {
+            ringingCall = getRingingCall(phone);
+            if (ringingCall != null) {
+                Rlog.i(TAG, "maybeRejectIncomingCall found a ringing call");
+                break;
+            }
+        }
+
+        if (ringingCall == null) {
+            if (completeConsumer != null) {
+                completeConsumer.accept(true);
+            }
+            return;
+        }
+
+        try {
+            ringingCall.hangup();
+            if (completeConsumer == null) return;
+
+            CompletableFuture<Boolean> future = new CompletableFuture<>();
+            com.android.internal.telephony.Connection cn = ringingCall.getLatestConnection();
+            cn.addListener(new OnDisconnectListener(future));
+            // A timeout that will complete the future to not block the outgoing call indefinitely.
+            CompletableFuture<Boolean> timeout = new CompletableFuture<>();
+            mHandler.postDelayed(
+                    () -> timeout.complete(false), DEFAULT_REJECT_INCOMING_CALL_TIMEOUT_MS);
+            // Ensure that the Consumer is completed on the main thread.
+            CompletableFuture<Void> unused = future.acceptEitherAsync(timeout, completeConsumer,
+                    mHandler::post).exceptionally((ex) -> {
+                        Rlog.w(TAG, "maybeRejectIncomingCall - exceptionally= " + ex);
+                        return null;
+                    });
+        } catch (Exception e) {
+            Rlog.w(TAG, "maybeRejectIncomingCall - exception= " + e.getMessage());
+            if (completeConsumer != null) {
+                completeConsumer.accept(false);
+            }
+        }
+    }
+
+    private void registerForNewRingingConnection() {
+        Phone[] phones = mPhoneFactoryProxy.getPhones();
+        if (phones == null) {
+            // unit testing
+            return;
+        }
+        for (Phone phone : phones) {
+            phone.registerForNewRingingConnection(mHandler, MSG_NEW_RINGING_CONNECTION,
+                    mRegistrantidentifier);
+        }
+    }
+
+    /**
+     * Hangup the new ringing call if there is an ongoing emergency call not connected.
+     */
+    private void handleNewRingingConnection(Message msg) {
+        Connection c = (Connection) ((AsyncResult) msg.obj).result;
+        if (c == null) return;
+        if ((mNormalRoutingEmergencyConnection == null
+                || mNormalRoutingEmergencyConnection.getState() == STATE_ACTIVE
+                || mNormalRoutingEmergencyConnection.getState() == STATE_DISCONNECTED)
+                && (mOngoingConnection == null
+                || mOngoingConnection.getState() == STATE_ACTIVE
+                || mOngoingConnection.getState() == STATE_DISCONNECTED)) {
+            return;
+        }
+        if ((c.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS)
+                && ((ImsPhoneConnection) c).isIncomingCallAutoRejected()) {
+            Rlog.i(TAG, "handleNewRingingConnection auto rejected call");
+        } else {
+            try {
+                Rlog.i(TAG, "handleNewRingingConnection silently drop incoming call");
+                c.getCall().hangup();
+            } catch (CallStateException e) {
+                Rlog.w(TAG, "handleNewRingingConnection", e);
+            }
+        }
+    }
+
+    /**
+     * Indicates the start of a normal routing emergency call.
+     *
+     * <p>
+     * Handles turning on radio and switching DDS.
+     *
+     * @param phone the {@code Phone} on which to process the emergency call.
+     * @param c the {@code Connection} on which to process the emergency call.
+     * @param completeConsumer The consumer to call once rejecting incoming call completes,
+     *        provides {@code true} result if operation completes successfully
+     *        or {@code false} if the operation timed out/failed.
+     */
+    public void startNormalRoutingEmergencyCall(@NonNull Phone phone,
+            @NonNull android.telecom.Connection c, @NonNull Consumer<Boolean> completeConsumer) {
+        Rlog.i(TAG, "startNormalRoutingEmergencyCall: phoneId=" + phone.getPhoneId()
+                + ", callId=" + c.getTelecomCallId());
+
+        mNormalRoutingEmergencyConnection = c;
+        maybeRejectIncomingCall(completeConsumer);
+    }
+
+    /**
+     * Indicates the termination of a normal routing emergency call.
+     *
+     * @param c the normal routing emergency call disconnected.
+     */
+    public void endNormalRoutingEmergencyCall(@NonNull android.telecom.Connection c) {
+        if (c != mNormalRoutingEmergencyConnection) return;
+        Rlog.i(TAG, "endNormalRoutingEmergencyCall: callId=" + c.getTelecomCallId());
+        mNormalRoutingEmergencyConnection = null;
+    }
+
+    /**
+     * Handles the normal routing emergency call state change.
+     *
+     * @param c the call whose state has changed
+     * @param state the new call state
+     */
+    public void onNormalRoutingEmergencyCallStateChanged(android.telecom.Connection c,
+            @android.telecom.Connection.ConnectionState int state) {
+        if (c != mNormalRoutingEmergencyConnection) return;
+
+        // If the call is connected, we don't need to monitor incoming call any more.
+        if (state == android.telecom.Connection.STATE_ACTIVE
+                || state == android.telecom.Connection.STATE_DISCONNECTED) {
+            endNormalRoutingEmergencyCall(c);
+        }
     }
 }
diff --git a/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
index 384112d..306f6bb 100644
--- a/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
+++ b/src/java/com/android/internal/telephony/emergency/RadioOnHelper.java
@@ -153,7 +153,7 @@
     private void powerOffSatellite(Phone phoneForEmergencyCall) {
         SatelliteController satelliteController = SatelliteController.getInstance();
         satelliteController.requestSatelliteEnabled(phoneForEmergencyCall.getSubId(),
-                false /* enableSatellite */, false /* enableDemoMode */,
+                false /* enableSatellite */, false /* enableDemoMode */, false /* isEmergency */,
                 new IIntegerConsumer.Stub() {
                     @Override
                     public void accept(int result) {
diff --git a/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java b/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java
index 5949f66..fa0610a 100644
--- a/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java
+++ b/src/java/com/android/internal/telephony/emergency/RadioOnStateListener.java
@@ -395,6 +395,7 @@
                 if (mSatelliteController.isSatelliteEnabled()) {
                     mSatelliteController.requestSatelliteEnabled(mPhone.getSubId(),
                             false /* enableSatellite */, false /* enableDemoMode */,
+                            false /* isEmergency*/,
                             new IIntegerConsumer.Stub() {
                                 @Override
                                 public void accept(int result) {
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index e527a14..1a5b99e 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -620,25 +620,32 @@
     void downloadSubscription(int cardId, int portIndex, DownloadableSubscription subscription,
             boolean switchAfterDownload, String callingPackage, boolean forceDeactivateSim,
             Bundle resolvedBundle, PendingIntent callbackIntent) {
-        boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
-        boolean callerCanDownloadAdminManagedSubscription =
-                Flags.esimManagementEnabled()
-                        && callerCanManageDevicePolicyManagedSubscriptions(callingPackage);
+        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
+
+        boolean callerHasAdminPrivileges = false;
         if (Flags.esimManagementEnabled()) {
-            if (mContext
-                    .getSystemService(UserManager.class)
-                    .hasUserRestriction(UserManager.DISALLOW_SIM_GLOBALLY)
-                    && !callerCanDownloadAdminManagedSubscription) {
+            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.
-                throw new SecurityException("Caller is not authorized to download subscriptions");
+                sendResult(callbackIntent, ERROR, null);
+                return;
             }
         }
-        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
         // Don't try to resolve the port index for apps which are not targeting on T for backward
         // compatibility. instead always use default port 0.
         boolean shouldResolvePortIndex = isCompatChangeEnabled(callingPackage,
                 EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS);
+        boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
 
         long token = Binder.clearCallingIdentity();
         try {
@@ -651,26 +658,19 @@
                 isConsentNeededToResolvePortIndex = (portIndex
                         == TelephonyManager.INVALID_PORT_INDEX);
             }
-            // Caller has admin privileges if they can download admin managed subscription,
-            // and are not switching the subscription after download (admins cannot silently
-            // enable the subscription).
-            boolean hasAdminPrivileges =
-                    callerCanDownloadAdminManagedSubscription && !switchAfterDownload;
             Log.d(TAG, " downloadSubscription cardId: " + cardId + " switchAfterDownload: "
-                    + switchAfterDownload + " portIndex: " + portIndex
-                    + " forceDeactivateSim: " + forceDeactivateSim + " callingPackage: "
-                    + callingPackage
+                    + switchAfterDownload + " portIndex: " + portIndex + " forceDeactivateSim: "
+                    + forceDeactivateSim + " callingPackage: " + callingPackage
                     + " isConsentNeededToResolvePortIndex: " + isConsentNeededToResolvePortIndex
                     + " shouldResolvePortIndex:" + shouldResolvePortIndex
-                    + " hasAdminPrivileges:" + hasAdminPrivileges);
-            if (!isConsentNeededToResolvePortIndex
-                    && (callerCanWriteEmbeddedSubscriptions
-                                    || hasAdminPrivileges)) {
+                    + " callerHasAdminPrivileges:" + callerHasAdminPrivileges);
+            if (!isConsentNeededToResolvePortIndex && (callerCanWriteEmbeddedSubscriptions
+                    || callerHasAdminPrivileges)) {
                 // With WRITE_EMBEDDED_SUBSCRIPTIONS, we can skip profile-specific permission checks
                 // and move straight to the profile download.
                 downloadSubscriptionPrivileged(cardId, portIndex, token, subscription,
                         switchAfterDownload, forceDeactivateSim, callingPackage, resolvedBundle,
-                        callbackIntent, callerCanDownloadAdminManagedSubscription,
+                        callbackIntent, callerHasAdminPrivileges,
                         getCurrentEmbeddedSubscriptionIds(cardId));
                 return;
             }
@@ -867,8 +867,11 @@
                                             cardId,
                                             existingSubscriptions);
                                     return;
+                                } else if (markAsOwnedByAdmin) {
+                                    refreshSubscriptionsOwnership(true, callingPackage, cardId,
+                                            existingSubscriptions);
                                 }
-                                break;
+                            break;
                             case EuiccService.RESULT_MUST_DEACTIVATE_SIM:
                                 resultCode = RESOLVABLE_ERROR;
                                 addResolutionIntentWithPort(extrasIntent,
@@ -1733,22 +1736,27 @@
         SubscriptionManagerService.getInstance().updateEmbeddedSubscriptions(
                 List.of(mTelephonyManager.getCardIdForDefaultEuicc()),
                 () -> {
-                    if (Flags.esimManagementEnabled() && isCallerAdmin) {
-                        // Mark the newly downloaded subscriptions as being owned by an admin so
-                        // that actions for that subscription can be restricted,
-                        // and the admin is limited to effecting only these subscriptions.
-                        Set<Integer> subscriptionsAfter = getCurrentEmbeddedSubscriptionIds(cardId);
-                        subscriptionsAfter.removeAll(subscriptionsBefore);
-                        for (int subId: subscriptionsAfter) {
-                            SubscriptionManagerService
-                                    .getInstance().setGroupOwner(subId, callingPackage);
-                        }
-                    }
+                    refreshSubscriptionsOwnership(isCallerAdmin, callingPackage, cardId,
+                            subscriptionsBefore);
                     sendResult(callbackIntent, resultCode, extrasIntent);
                 });
 
     }
 
+    private void refreshSubscriptionsOwnership(boolean isCallerAdmin, String callingPackage,
+            int cardId, Set<Integer> subscriptionsBefore) {
+        if (Flags.esimManagementEnabled() && isCallerAdmin) {
+            // Mark the newly downloaded subscriptions as being owned by an admin so
+            // that actions for that subscription can be restricted,
+            // and the admin is limited to effecting only these subscriptions.
+            Set<Integer> subscriptionsAfter = getCurrentEmbeddedSubscriptionIds(cardId);
+            subscriptionsAfter.removeAll(subscriptionsBefore);
+            for (int subId : subscriptionsAfter) {
+                SubscriptionManagerService.getInstance().setGroupOwner(subId, callingPackage);
+            }
+        }
+    }
+
     /** Dispatch the given callback intent with the given result code and data. */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     public void sendResult(PendingIntent callbackIntent, int resultCode, Intent extrasIntent) {
@@ -2186,20 +2194,31 @@
     }
 
     private boolean callerCanManageDevicePolicyManagedSubscriptions(String callingPackage) {
-        // isProfileOwner/isDeviceOwner needs to callers user, so create device policy manager
-        // with the correct context associated with the caller.
+        DevicePolicyManager devicePolicyManager = getDevicePolicyManager();
+        boolean isAdmin =
+                devicePolicyManager != null && (devicePolicyManager.isProfileOwnerApp(
+                        callingPackage)
+                        || devicePolicyManager.isDeviceOwnerApp(callingPackage));
+        return isAdmin || mContext.checkCallingOrSelfPermission(
+                Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private boolean shouldAllowSwitchAfterDownload(String callingPackage) {
+        DevicePolicyManager devicePolicyManager = getDevicePolicyManager();
+        return devicePolicyManager != null && (devicePolicyManager.isDeviceOwnerApp(callingPackage)
+                || (devicePolicyManager.isProfileOwnerApp(callingPackage)
+                && devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()));
+    }
+
+    private DevicePolicyManager getDevicePolicyManager() {
+        // create device policy manager with the correct context associated with the caller.
         DevicePolicyManager devicePolicyManager =
                 retrieveDevicePolicyManagerFromUserContext(Binder.getCallingUserHandle());
         if (devicePolicyManager == null) {
             Log.w(TAG, "Unable to get device policy manager");
-            return false;
         }
-        boolean isAdmin =
-                devicePolicyManager.isProfileOwnerApp(callingPackage)
-                        || devicePolicyManager.isDeviceOwnerApp(callingPackage);
-        return isAdmin || mContext.checkCallingOrSelfPermission(
-                Manifest.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS)
-                == PackageManager.PERMISSION_GRANTED;
+        return devicePolicyManager;
     }
 
     @Override
diff --git a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
index 9de3ee9..c003405 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -200,6 +200,12 @@
          10 = dialing number
 */
 
+    /**
+     * An FAC code is of the following format:
+     * #FAC#MSISDN*
+     */
+    static Pattern sFac = Pattern.compile("^\\#\\d+\\#[^*]+\\*$");
+
     static final int MATCH_GROUP_POUND_STRING = 1;
 
     static final int MATCH_GROUP_ACTION = 2;
@@ -274,7 +280,9 @@
                 // in India operator(Mumbai MTNL)
                 ret = new GsmMmiCode(phone, app);
                 ret.mPoundString = dialString;
-            } else if (ret.isFacToDial()) {
+            } else if (ret.isFacToDial(dialString)) {
+                // Note: we had to pass in the dial string above because the full dial string is not
+                // in the MmiCode class (or even needed there).
                 // This is a FAC (feature access code) to dial as a normal call.
                 ret = null;
             }
@@ -963,12 +971,28 @@
     }
 
     /**
+     * Determines if a full dial string matches the general format of a FAC code.
+     * Ie. #FAC#MSIDN*
+     * @param dialString The full dialed number.
+     * @return {@code true} if the dialed number has the general format of a FAC code.
+     */
+    private static boolean isFacFormatNumber(String dialString) {
+        Matcher m = sFac.matcher(dialString);
+        return m.matches();
+    }
+
+    /**
      * Returns true if the Service Code is FAC to dial as a normal call.
      *
      * FAC stands for feature access code and it is special patterns of characters
      * to invoke certain features.
      */
-    private boolean isFacToDial() {
+    private boolean isFacToDial(String dialString) {
+        if (!isFacFormatNumber(dialString)) {
+            // If the full dial string doesn't conform to the required format for a FAC, we will
+            // bail early. It is likely a true USSD which shares the same code as the FAC.
+            return false;
+        }
         CarrierConfigManager configManager = (CarrierConfigManager)
                 mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
         PersistableBundle b = configManager.getConfigForSubId(mPhone.getSubId());
diff --git a/src/java/com/android/internal/telephony/ims/ImsResolver.java b/src/java/com/android/internal/telephony/ims/ImsResolver.java
index 49b7e62..eb389b7 100644
--- a/src/java/com/android/internal/telephony/ims/ImsResolver.java
+++ b/src/java/com/android/internal/telephony/ims/ImsResolver.java
@@ -60,6 +60,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.telephony.PhoneConfigurationManager;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.FileDescriptor;
@@ -139,11 +140,12 @@
      * Create the ImsResolver Service singleton instance.
      */
     public static void make(Context context, String defaultMmTelPackageName,
-            String defaultRcsPackageName, int numSlots, ImsFeatureBinderRepository repo) {
+            String defaultRcsPackageName, int numSlots, ImsFeatureBinderRepository repo,
+            FeatureFlags featureFlags) {
         if (sInstance == null) {
             sHandlerThread.start();
             sInstance = new ImsResolver(context, defaultMmTelPackageName, defaultRcsPackageName,
-                    numSlots, repo, sHandlerThread.getLooper());
+                    numSlots, repo, sHandlerThread.getLooper(), featureFlags);
         }
     }
 
@@ -372,7 +374,7 @@
          */
         ImsServiceController create(Context context, ComponentName componentName,
                 ImsServiceController.ImsServiceControllerCallbacks callbacks,
-                ImsFeatureBinderRepository repo);
+                ImsFeatureBinderRepository repo, FeatureFlags featureFlags);
     }
 
     private ImsServiceControllerFactory mImsServiceControllerFactory =
@@ -386,8 +388,9 @@
         @Override
         public ImsServiceController create(Context context, ComponentName componentName,
                 ImsServiceController.ImsServiceControllerCallbacks callbacks,
-                ImsFeatureBinderRepository repo) {
-            return new ImsServiceController(context, componentName, callbacks, repo);
+                ImsFeatureBinderRepository repo, FeatureFlags featureFlags) {
+                    return new ImsServiceController(context, componentName, callbacks, repo,
+                            featureFlags);
         }
     };
 
@@ -410,7 +413,7 @@
                 @Override
                 public ImsServiceController create(Context context, ComponentName componentName,
                         ImsServiceController.ImsServiceControllerCallbacks callbacks,
-                        ImsFeatureBinderRepository repo) {
+                        ImsFeatureBinderRepository repo, FeatureFlags featureFlags) {
                     return new ImsServiceControllerCompat(context, componentName, callbacks, repo);
                 }
             };
@@ -445,6 +448,9 @@
     // Synchronize all events on a handler to ensure that the cache includes the most recent
     // version of the installed ImsServices.
     private final Handler mHandler;
+
+    private final FeatureFlags mFeatureFlags;
+
     private class ResolverHandler extends Handler {
 
         ResolverHandler(Looper looper) {
@@ -581,7 +587,7 @@
 
     public ImsResolver(Context context, String defaultMmTelPackageName,
             String defaultRcsPackageName, int numSlots, ImsFeatureBinderRepository repo,
-            Looper looper) {
+            Looper looper, FeatureFlags featureFlags) {
         Log.i(TAG, "device MMTEL package: " + defaultMmTelPackageName + ", device RCS package:"
                 + defaultRcsPackageName);
         mContext = context;
@@ -591,6 +597,7 @@
 
         mHandler = new ResolverHandler(looper);
         mRunnableExecutor = new HandlerExecutor(mHandler);
+        mFeatureFlags = featureFlags;
         mCarrierServices = new SparseArray<>(mNumSlots);
         setDeviceConfiguration(defaultMmTelPackageName, ImsFeature.FEATURE_EMERGENCY_MMTEL);
         setDeviceConfiguration(defaultMmTelPackageName, ImsFeature.FEATURE_MMTEL);
@@ -1233,7 +1240,8 @@
                     Log.w(TAG, "bindImsService: error=" + e.getMessage());
                 }
             } else {
-                controller = info.controllerFactory.create(mContext, info.name, this, mRepo);
+                controller = info.controllerFactory.create(mContext, info.name, this, mRepo,
+                        mFeatureFlags);
                 Log.i(TAG, "Binding ImsService: " + controller.getComponentName()
                         + " with features: " + features);
                 controller.bind(features, slotIdToSubIdMap);
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceController.java b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
index 6af7a08..ea8399f 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
@@ -49,6 +49,7 @@
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.ExponentialBackoff;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.io.PrintWriter;
@@ -265,6 +266,7 @@
     private final HandlerThread mHandlerThread = new HandlerThread("ImsServiceControllerHandler");
     private final Handler mHandler;
     private final LegacyPermissionManager mPermissionManager;
+    private final FeatureFlags mFeatureFlags;
     private ImsFeatureBinderRepository mRepo;
     private ImsServiceControllerCallbacks mCallbacks;
     private ExponentialBackoff mBackoff;
@@ -353,7 +355,8 @@
     };
 
     public ImsServiceController(Context context, ComponentName componentName,
-            ImsServiceControllerCallbacks callbacks, ImsFeatureBinderRepository repo) {
+            ImsServiceControllerCallbacks callbacks, ImsFeatureBinderRepository repo,
+            FeatureFlags featureFlags) {
         mContext = context;
         mComponentName = componentName;
         mCallbacks = callbacks;
@@ -369,6 +372,7 @@
                 Context.LEGACY_PERMISSION_SERVICE);
         mRepo = repo;
         mImsEnablementTracker = new ImsEnablementTracker(mHandlerThread.getLooper(), componentName);
+        mFeatureFlags = featureFlags;
         mPackageManager = mContext.getPackageManager();
         if (mPackageManager != null) {
             mChangedPackages = mPackageManager.getChangedPackages(mLastSequenceNumber);
@@ -383,7 +387,7 @@
     // testing, use a handler supplied by the testing system.
     public ImsServiceController(Context context, ComponentName componentName,
             ImsServiceControllerCallbacks callbacks, Handler handler, RebindRetry rebindRetry,
-            ImsFeatureBinderRepository repo) {
+            ImsFeatureBinderRepository repo, FeatureFlags featureFlags) {
         mContext = context;
         mComponentName = componentName;
         mCallbacks = callbacks;
@@ -396,6 +400,7 @@
                 mRestartImsServiceRunnable);
         mPermissionManager = null;
         mRepo = repo;
+        mFeatureFlags = featureFlags;
         mImsEnablementTracker = new ImsEnablementTracker(handler.getLooper(), componentName);
     }
 
@@ -493,6 +498,12 @@
         synchronized (mLock) {
             HashSet<Integer> slotIDs = newImsFeatures.stream().map(e -> e.slotId).collect(
                     Collectors.toCollection(HashSet::new));
+
+            // Set the number of slot for IMS enable for each slot
+            if (mFeatureFlags.setNumberOfSimForImsEnable()) {
+                mImsEnablementTracker.setNumOfSlots(slotIDs.size());
+            }
+
             // detect which subIds have changed on a per-slot basis
             SparseIntArray changedSubIds = new SparseIntArray(slotIDs.size());
             for (Integer slotID : slotIDs) {
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java b/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
index 778bd0e..440d784 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceControllerCompat.java
@@ -38,6 +38,7 @@
 import com.android.ims.internal.IImsMMTelFeature;
 import com.android.ims.internal.IImsServiceController;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlagsImpl;
 
 /**
  * Manages the Binding lifecycle of one ImsService as well as the relevant ImsFeatures that the
@@ -76,7 +77,7 @@
     public ImsServiceControllerCompat(Context context, ComponentName componentName,
             ImsServiceController.ImsServiceControllerCallbacks callbacks,
             ImsFeatureBinderRepository repo) {
-        super(context, componentName, callbacks, repo);
+        super(context, componentName, callbacks, repo, new FeatureFlagsImpl());
         mMmTelFeatureFactory = MmTelFeatureCompatAdapter::new;
     }
 
@@ -84,7 +85,8 @@
     public ImsServiceControllerCompat(Context context, ComponentName componentName,
             ImsServiceControllerCallbacks callbacks, Handler handler, RebindRetry rebindRetry,
             ImsFeatureBinderRepository repo, MmTelFeatureCompatFactory factory) {
-        super(context, componentName, callbacks, handler, rebindRetry, repo);
+        super(context, componentName, callbacks, handler, rebindRetry, repo,
+                new FeatureFlagsImpl());
         mMmTelFeatureFactory = factory;
     }
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index a6bb1d6..10cbe77 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -21,6 +21,7 @@
 import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE;
 import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED;
 import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERED;
+import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERING;
 import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_NONE;
 import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_CLEAR_RAT_BLOCKS;
 import static android.telephony.ims.RegistrationManager.SUGGESTED_ACTION_TRIGGER_PLMN_BLOCK;
@@ -75,6 +76,7 @@
 import android.os.UserHandle;
 import android.preference.PreferenceManager;
 import android.sysprop.TelephonyProperties;
+import android.telecom.VideoProfile;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
 import android.telephony.NetworkRegistrationInfo;
@@ -263,6 +265,14 @@
         }
     }
 
+    /**
+     * Container to transfer IMS registration radio tech.
+     * This will be used as result value of AsyncResult to the handler that called
+     * {@link #registerForImsRegistrationChanges(Handler, int, Object)}
+     */
+    public record ImsRegistrationRadioTechInfo(int phoneId, int imsRegistrationTech,
+                                                int imsRegistrationState) {}
+
     // Instance Variables
     Phone mDefaultPhone;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -802,7 +812,11 @@
             try {
                 if (getRingingCall().getState() != ImsPhoneCall.State.IDLE) {
                     if (DBG) logd("MmiCode 2: accept ringing call");
-                    mCT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
+                    if (mFeatureFlags.answerAudioOnlyWhenAnsweringViaMmiCode()) {
+                        mCT.acceptCall(VideoProfile.STATE_AUDIO_ONLY);
+                    } else {
+                        mCT.acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
+                    }
                 } else if (getBackgroundCall().getState() == ImsPhoneCall.State.HOLDING) {
                     // If there's an active ongoing call as well, hold it and the background one
                     // should automatically unhold. Otherwise just unhold the background call.
@@ -2504,7 +2518,15 @@
             updateImsRegistrationInfo(REGISTRATION_STATE_REGISTERED,
                     attributes.getRegistrationTechnology(), SUGGESTED_ACTION_NONE,
                     imsTransportType);
-            AsyncResult ar = new AsyncResult(null, null, null);
+
+            AsyncResult ar;
+            if (mFeatureFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
+                ar = new AsyncResult(null, new ImsRegistrationRadioTechInfo(mPhoneId,
+                        attributes.getRegistrationTechnology(), REGISTRATION_STATE_REGISTERED),
+                        null);
+            } else {
+                ar = new AsyncResult(null, null, null);
+            }
             mImsRegistrationUpdateRegistrants.notifyRegistrants(ar);
         }
 
@@ -2521,7 +2543,15 @@
             mMetrics.writeOnImsConnectionState(mPhoneId, ImsConnectionState.State.PROGRESSING,
                     null);
             mImsStats.onImsRegistering(imsRadioTech);
-            AsyncResult ar = new AsyncResult(null, null, null);
+
+            AsyncResult ar;
+            if (mFeatureFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
+                ar = new AsyncResult(null, new ImsRegistrationRadioTechInfo(mPhoneId,
+                        imsRadioTech, REGISTRATION_STATE_REGISTERING),
+                        null);
+            } else {
+                ar = new AsyncResult(null, null, null);
+            }
             mImsRegistrationUpdateRegistrants.notifyRegistrants(ar);
         }
 
@@ -2564,7 +2594,15 @@
                 setCurrentSubscriberUris(null);
                 clearPhoneNumberForSourceIms();
             }
-            AsyncResult ar = new AsyncResult(null, null, null);
+
+            AsyncResult ar;
+            if (mFeatureFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
+                ar = new AsyncResult(null, new ImsRegistrationRadioTechInfo(mPhoneId,
+                        REGISTRATION_TECH_NONE, REGISTRATION_STATE_NOT_REGISTERED),
+                        null);
+            } else {
+                ar = new AsyncResult(null, null, null);
+            }
             mImsRegistrationUpdateRegistrants.notifyRegistrants(ar);
         }
 
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index dcb3b20..e73eafd 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -3553,8 +3553,10 @@
             if (DBG) log("onCallStartFailed reasonCode=" + reasonInfo.getCode());
 
             int eccCategory = EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED;
+            List<String> emergencyUrns = new ArrayList<>();
             if (imsCall != null && imsCall.getCallProfile() != null) {
                 eccCategory = imsCall.getCallProfile().getEmergencyServiceCategories();
+                emergencyUrns = imsCall.getCallProfile().getEmergencyUrns();
             }
 
             if (mHoldSwitchingState == HoldSwapState.HOLDING_TO_ANSWER_INCOMING) {
@@ -3581,13 +3583,14 @@
                 // Since onCallInitiating and onCallProgressing reset mPendingMO,
                 // we can't depend on mPendingMO.
                 if (conn != null) {
-                    logi("onCallStartFailed eccCategory=" + eccCategory);
+                    logi("onCallStartFailed eccCategory=" + eccCategory + ", emergencyUrns="
+                            + emergencyUrns);
                     int reason = reasonInfo.getCode();
                     int extraCode = reasonInfo.getExtraCode();
                     if ((reason == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
                             && extraCode == ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY)
                             || (reason == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL)) {
-                        conn.setNonDetectableEmergencyCallInfo(eccCategory);
+                        conn.setNonDetectableEmergencyCallInfo(eccCategory, emergencyUrns);
                     }
                     conn.setImsReasonInfo(reasonInfo);
                     sendCallStartFailedDisconnect(imsCall, reasonInfo);
@@ -3765,11 +3768,13 @@
                     && DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
                 if (conn != null) {
                     int eccCategory = EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED;
+                    List<String> emergencyUrns = new ArrayList<>();
                     if (imsCall != null && imsCall.getCallProfile() != null) {
                         eccCategory = imsCall.getCallProfile().getEmergencyServiceCategories();
+                        emergencyUrns = imsCall.getCallProfile().getEmergencyUrns();
                         logi("onCallTerminated eccCategory=" + eccCategory);
                     }
-                    conn.setNonDetectableEmergencyCallInfo(eccCategory);
+                    conn.setNonDetectableEmergencyCallInfo(eccCategory, emergencyUrns);
                 }
                 processCallStateChange(imsCall, ImsPhoneCall.State.DISCONNECTED, cause);
                 return;
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
old mode 100755
new mode 100644
index a71355d..316f62a
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -157,6 +157,11 @@
      */
     private boolean mIsHeldByRemote = false;
 
+    /**
+     * Used to indicate if both the user and carrier config have enabled the business composer.
+     */
+    private boolean mIsBusinessComposerFeatureEnabled = false;
+
     //***** Event Constants
     private static final int EVENT_DTMF_DONE = 1;
     private static final int EVENT_PAUSE_DONE = 2;
@@ -230,6 +235,10 @@
         mCreateTime = System.currentTimeMillis();
         mUusInfo = null;
 
+        if (com.android.server.telecom.flags.Flags.businessCallComposer()) {
+            setIsBusinessComposerFeatureEnabled(phone);
+        }
+
         // Ensure any extras set on the ImsCallProfile at the start of the call are cached locally
         // in the ImsPhoneConnection.  This isn't going to inform any listeners (since the original
         // connection is not likely to be associated with a TelephonyConnection yet).
@@ -277,13 +286,13 @@
 
         mIsEmergency = isEmergency;
         if (isEmergency) {
-            setEmergencyCallInfo(mOwner);
+            setEmergencyCallInfo(mOwner, dialArgs);
 
             if (getEmergencyNumberInfo() == null) {
                 // 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
                 // non-detectable emergency call from IMS.
-                setNonDetectableEmergencyCallInfo(dialArgs.eccCategory);
+                setNonDetectableEmergencyCallInfo(dialArgs.eccCategory, new ArrayList<String>());
             }
         }
 
@@ -1389,10 +1398,18 @@
      * ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME). This helper notifies Telecom of the business
      * composer values which will then be injected into the android.telecom.Call object.
      */
-    private void maybeInjectBusinessComposerExtras(Bundle extras) {
+    @VisibleForTesting
+    public void maybeInjectBusinessComposerExtras(Bundle extras) {
         if (extras == null) {
             return;
         }
+        // Telephony should check that the business composer features is on BEFORE
+        // propagating the business call extras.  This prevents the user from getting
+        // business call info when they turned the feature off.
+        if (!mIsBusinessComposerFeatureEnabled) {
+            Rlog.i(LOG_TAG, "mIBCE: business composer feature is NOT enabled");
+            return;
+        }
         try {
             if (extras.containsKey(ImsCallProfile.EXTRA_IS_BUSINESS_CALL)
                     && !extras.containsKey(android.telecom.Call.EXTRA_IS_BUSINESS_CALL)) {
@@ -1413,6 +1430,60 @@
         }
     }
 
+    @VisibleForTesting
+    public boolean getIsBusinessComposerFeatureEnabled() {
+        return mIsBusinessComposerFeatureEnabled;
+    }
+
+    @VisibleForTesting
+    public void setIsBusinessComposerFeatureEnabled(Phone phone) {
+        mIsBusinessComposerFeatureEnabled = isBusinessComposerEnabledByConfig(phone)
+                && isBusinessOnlyCallComposerEnabledByUser(phone);
+        Rlog.i(LOG_TAG, String.format(
+                "setIsBusinessComposerFeatureEnabled:  mIsBusinessComposerFeatureEnabled=[%b], "
+                        + "phone=[%s]", mIsBusinessComposerFeatureEnabled, phone));
+    }
+
+    /**
+     * Returns whether the carrier supports and has enabled the business composer
+     */
+    @VisibleForTesting
+    public boolean isBusinessComposerEnabledByConfig(Phone phone) {
+        PersistableBundle b = null;
+        CarrierConfigManager configMgr = phone.getContext().getSystemService(
+                CarrierConfigManager.class);
+
+        if (configMgr != null) {
+            // If an invalid subId is used, this bundle will contain default values.
+            b = configMgr.getConfigForSubId(phone.getSubId());
+        }
+        if (b != null) {
+            return b.getBoolean(CarrierConfigManager.KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL);
+        } else {
+            // Return static default defined in CarrierConfigManager.
+            return CarrierConfigManager.getDefaultConfig()
+                    .getBoolean(CarrierConfigManager.KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL);
+        }
+    }
+
+    /**
+     * Returns whether the user has enabled the business composer
+     */
+    @VisibleForTesting
+    public boolean isBusinessOnlyCallComposerEnabledByUser(Phone phone) {
+        if (phone == null || phone.getContext() == null) {
+            return false;
+        }
+        TelephonyManager tm = (TelephonyManager)
+                phone.getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        if (tm == null) {
+            Rlog.e(LOG_TAG, "isBusinessOnlyCallComposerEnabledByUser: TelephonyManager is null");
+            return false;
+        }
+        return tm.getCallComposerStatus() == TelephonyManager.CALL_COMPOSER_STATUS_BUSINESS_ONLY
+                || tm.getCallComposerStatus() == TelephonyManager.CALL_COMPOSER_STATUS_ON;
+    }
+
     private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
         if (extras == null || newExtras == null) {
             return extras == newExtras;
diff --git a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
index e1f6309..175f5e4 100644
--- a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
@@ -41,6 +41,7 @@
 import com.android.internal.telephony.ServiceStateTracker;
 import com.android.internal.telephony.data.DataNetwork;
 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
+import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
 import com.android.telephony.Rlog;
@@ -64,10 +65,12 @@
     public static final int SIZE_LIMIT_HANDOVER_FAILURES = 15;
 
     private final DefaultNetworkMonitor mDefaultNetworkMonitor;
+    private final SatelliteController mSatelliteController;
 
     public DataCallSessionStats(Phone phone) {
         mPhone = phone;
         mDefaultNetworkMonitor = PhoneFactory.getMetricsCollector().getDefaultNetworkMonitor();
+        mSatelliteController = SatelliteController.getInstance();
     }
 
     private boolean isSystemDefaultNetworkMobile() {
@@ -76,8 +79,9 @@
     }
 
     /** Creates a new ongoing atom when data call is set up. */
-    public synchronized void onSetupDataCall(@ApnType int apnTypeBitMask) {
-        mDataCallSession = getDefaultProto(apnTypeBitMask);
+    public synchronized void onSetupDataCall(@ApnType int apnTypeBitMask,
+            boolean isSatellite) {
+        mDataCallSession = getDefaultProto(apnTypeBitMask, isSatellite);
         mStartTime = getTimeMillis();
         PhoneFactory.getMetricsCollector().registerOngoingDataCallStat(this);
     }
@@ -303,11 +307,15 @@
                 call.handoverFailureRat.length);
         copy.isNonDds = call.isNonDds;
         copy.isIwlanCrossSim = call.isIwlanCrossSim;
+        copy.isNtn = call.isNtn;
+        copy.isSatelliteTransport = call.isSatelliteTransport;
+        copy.isProvisioningProfile = call.isProvisioningProfile;
         return copy;
     }
 
     /** Creates a proto for a normal {@code DataCallSession} with default values. */
-    private DataCallSession getDefaultProto(@ApnType int apnTypeBitmask) {
+    private DataCallSession getDefaultProto(@ApnType int apnTypeBitmask,
+            boolean isSatellite) {
         DataCallSession proto = new DataCallSession();
         proto.dimension = RANDOM.nextInt();
         proto.isMultiSim = SimSlotState.isMultiSim();
@@ -329,6 +337,10 @@
         proto.handoverFailureRat = new int[0];
         proto.isNonDds = false;
         proto.isIwlanCrossSim = false;
+        proto.isNtn = mSatelliteController != null
+                ? mSatelliteController.isInSatelliteModeForCarrierRoaming(mPhone) : false;
+        proto.isSatelliteTransport = isSatellite;
+        proto.isProvisioningProfile = getIsProvisioningProfile();
         return proto;
     }
 
@@ -345,6 +357,17 @@
         return subInfo != null && subInfo.isOpportunistic();
     }
 
+    private boolean getIsProvisioningProfile() {
+        SubscriptionInfoInternal subInfo = SubscriptionManagerService.getInstance()
+                .getSubscriptionInfoInternal(mPhone.getSubId());
+        try {
+            return subInfo.getProfileClass() == SubscriptionManager.PROFILE_CLASS_PROVISIONING;
+        } catch (Exception ex) {
+            loge("getIsProvisioningProfile: " + ex.getMessage());
+            return false;
+        }
+    }
+
     private boolean getIsOos() {
         ServiceStateTracker serviceStateTracker = mPhone.getServiceStateTracker();
         ServiceState serviceState =
diff --git a/src/java/com/android/internal/telephony/metrics/DataConnectionStateTracker.java b/src/java/com/android/internal/telephony/metrics/DataConnectionStateTracker.java
index c7ef625..079ff03 100644
--- a/src/java/com/android/internal/telephony/metrics/DataConnectionStateTracker.java
+++ b/src/java/com/android/internal/telephony/metrics/DataConnectionStateTracker.java
@@ -16,9 +16,12 @@
 
 package com.android.internal.telephony.metrics;
 
+import static com.android.internal.telephony.flags.Flags.dataRatMetricEnabled;
+
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
+import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseDataConnectionState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyCallback;
@@ -27,6 +30,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyStatsLog;
 
 import java.util.HashMap;
 import java.util.List;
@@ -47,7 +51,10 @@
     private int mSubId;
     private HashMap<Integer, PreciseDataConnectionState> mLastPreciseDataConnectionState =
             new HashMap<>();
-    private PreciseDataConnectionStateListenerImpl mDataConnectionStateListener;
+    private TelephonyListenerImpl mTelephonyListener;
+    private int mActiveDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private int mChannelCountEnum = TelephonyStatsLog
+            .CONNECTED_CHANNEL_CHANGED__CONNECTED_CHANNEL_COUNT__CHANNEL_COUNT_UNSPECIFIED;
 
     private final SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionsChangedListener =
             new SubscriptionManager.OnSubscriptionsChangedListener() {
@@ -132,15 +139,15 @@
         TelephonyManager telephonyManager =
                 mPhone.getContext().getSystemService(TelephonyManager.class);
         if (telephonyManager != null) {
-            mDataConnectionStateListener = new PreciseDataConnectionStateListenerImpl(mExecutor);
-            mDataConnectionStateListener.register(telephonyManager.createForSubscriptionId(subId));
+            mTelephonyListener = new TelephonyListenerImpl(mExecutor);
+            mTelephonyListener.register(telephonyManager.createForSubscriptionId(subId));
         }
     }
 
     private void unregisterTelephonyListener() {
-        if (mDataConnectionStateListener != null) {
-            mDataConnectionStateListener.unregister();
-            mDataConnectionStateListener = null;
+        if (mTelephonyListener != null) {
+            mTelephonyListener.unregister();
+            mTelephonyListener = null;
         }
     }
 
@@ -156,12 +163,46 @@
         mPhone.getVoiceCallSessionStats().onPreciseDataConnectionStateChanged(connectionState);
     }
 
-    private class PreciseDataConnectionStateListenerImpl extends TelephonyCallback
-            implements TelephonyCallback.PreciseDataConnectionStateListener {
+    static int getActiveDataSubId() {
+        if (sDataConnectionStateTracker.size() == 0) {
+            return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+        }
+        return sDataConnectionStateTracker.valueAt(0).mActiveDataSubId;
+    }
+
+    /**
+     * Log RAT if the active data subId changes to another subId with a different RAT.
+     *
+     * @param subId the current active data subId
+     */
+    private void logRATChanges(int subId) {
+        if (mSubId == subId && mActiveDataSubId != subId) {
+            int newDataRat = mPhone.getServiceStateTracker()
+                    .getServiceStateStats().getCurrentDataRat();
+            for (int i = 0; i < sDataConnectionStateTracker.size(); i++) {
+                DataConnectionStateTracker dataConnectionStateTracker =
+                        sDataConnectionStateTracker.valueAt(0);
+                if (dataConnectionStateTracker.mSubId == mActiveDataSubId) {
+                    int previousDataRat = dataConnectionStateTracker.mPhone
+                            .getServiceStateTracker().getServiceStateStats()
+                            .getCurrentDataRat();
+                    if (newDataRat != previousDataRat) {
+                        TelephonyStatsLog.write(TelephonyStatsLog.DATA_RAT_STATE_CHANGED,
+                                newDataRat);
+                    }
+                }
+            }
+        }
+    }
+
+    private class TelephonyListenerImpl extends TelephonyCallback
+            implements TelephonyCallback.PreciseDataConnectionStateListener,
+            TelephonyCallback.ActiveDataSubscriptionIdListener,
+            TelephonyCallback.PhysicalChannelConfigListener {
         private final Executor mExecutor;
         private TelephonyManager mTelephonyManager = null;
 
-        PreciseDataConnectionStateListenerImpl(Executor executor) {
+        TelephonyListenerImpl(Executor executor) {
             mExecutor = executor;
         }
 
@@ -185,5 +226,58 @@
                 PreciseDataConnectionState connectionState) {
             notifyDataConnectionStateChanged(connectionState);
         }
+
+        @Override
+        public void onActiveDataSubscriptionIdChanged(int subId) {
+            if (dataRatMetricEnabled()) {
+                logRATChanges(subId);
+            }
+            mActiveDataSubId = subId;
+        }
+
+        @Override
+        public void onPhysicalChannelConfigChanged(List<PhysicalChannelConfig> configs) {
+            logChannelChange(configs);
+        }
+
+        /** Log channel number if it changes for active data subscription*/
+        private void logChannelChange(List<PhysicalChannelConfig> configs) {
+            int connectedChannelCount = configs.size();
+            int channelCountEnum = TelephonyStatsLog
+                    .CONNECTED_CHANNEL_CHANGED__CONNECTED_CHANNEL_COUNT__CHANNEL_COUNT_UNSPECIFIED;
+            switch(connectedChannelCount) {
+                case 0:
+                    channelCountEnum = TelephonyStatsLog
+                            .CONNECTED_CHANNEL_CHANGED__CONNECTED_CHANNEL_COUNT__CHANNEL_COUNT_ONE;
+                    break;
+                case 1:
+                    channelCountEnum = TelephonyStatsLog
+                            .CONNECTED_CHANNEL_CHANGED__CONNECTED_CHANNEL_COUNT__CHANNEL_COUNT_ONE;
+                    break;
+                case 2:
+                    channelCountEnum = TelephonyStatsLog
+                            .CONNECTED_CHANNEL_CHANGED__CONNECTED_CHANNEL_COUNT__CHANNEL_COUNT_TWO;
+                    break;
+                case 3:
+                    channelCountEnum = TelephonyStatsLog
+                            .CONNECTED_CHANNEL_CHANGED__CONNECTED_CHANNEL_COUNT__CHANNEL_COUNT_THREE;
+                    break;
+                case 4:
+                    channelCountEnum = TelephonyStatsLog
+                            .CONNECTED_CHANNEL_CHANGED__CONNECTED_CHANNEL_COUNT__CHANNEL_COUNT_FOUR;
+                    break;
+                // Greater than 4
+                default:
+                    channelCountEnum = TelephonyStatsLog
+                            .CONNECTED_CHANNEL_CHANGED__CONNECTED_CHANNEL_COUNT__CHANNEL_COUNT_FIVE;
+            }
+            if (mChannelCountEnum != channelCountEnum) {
+                if (mSubId != mActiveDataSubId) {
+                    TelephonyStatsLog.write(TelephonyStatsLog.CONNECTED_CHANNEL_CHANGED,
+                            channelCountEnum);
+                }
+                mChannelCountEnum = channelCountEnum;
+            }
+        }
     }
 }
diff --git a/src/java/com/android/internal/telephony/metrics/DataNetworkValidationStats.java b/src/java/com/android/internal/telephony/metrics/DataNetworkValidationStats.java
new file mode 100644
index 0000000..fdac834
--- /dev/null
+++ b/src/java/com/android/internal/telephony/metrics/DataNetworkValidationStats.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.metrics;
+
+import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.telephony.Annotation.ApnType;
+import android.telephony.Annotation.NetworkType;
+import android.telephony.PreciseDataConnectionState;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyStatsLog;
+import com.android.internal.telephony.nano.PersistAtomsProto.DataNetworkValidation;
+
+/**
+ * DataNetworkValidationStats logs the atoms for response after a validation request from the
+ * DataNetwork in framework.
+ */
+public class DataNetworkValidationStats {
+
+    private static final String TAG = DataNetworkValidationStats.class.getSimpleName();
+
+    @NonNull
+    private final Phone mPhone;
+
+    @NonNull
+    private final PersistAtomsStorage mAtomsStorage =
+            PhoneFactory.getMetricsCollector().getAtomsStorage();
+
+    @Nullable
+    private DataNetworkValidation mDataNetworkValidation;
+
+    @ElapsedRealtimeLong
+    private long mRequestedTimeInMillis;
+
+    /** constructor */
+    public DataNetworkValidationStats(@NonNull Phone phone) {
+        mPhone = phone;
+    }
+
+
+    /**
+     * Create a new ongoing atom when NetworkValidation requested.
+     *
+     * Create a data network validation proto for a new atom record and write the start time to
+     * calculate the elapsed time required.
+     *
+     * @param apnTypeBitMask APN type bitmask of DataNetwork.
+     */
+    public void onRequestNetworkValidation(@ApnType int apnTypeBitMask) {
+        if (mDataNetworkValidation == null) {
+            mDataNetworkValidation = getDefaultProto(apnTypeBitMask);
+            mRequestedTimeInMillis = getTimeMillis();
+        }
+    }
+
+    /** Mark the Handover Attempt field as true if validation was requested */
+    public void onHandoverAttempted() {
+        if (mDataNetworkValidation != null) {
+            mDataNetworkValidation.handoverAttempted = true;
+        }
+    }
+
+    /**
+     * Called when data network is disconnected.
+     *
+     * Since network validation is based on the data network, validation must also end when the data
+     * network is disconnected. At this time, validation has not been completed, save an atom as
+     * unspecified. and clear.
+     *
+     * @param networkType Current Network Type of the Data Network.
+     */
+    public void onDataNetworkDisconnected(@NetworkType int networkType) {
+        // Nothing to do, if never requested validation
+        if (mDataNetworkValidation == null) {
+            return;
+        }
+
+        // Set data for and atom.
+        calcElapsedTime();
+        mDataNetworkValidation.networkType = networkType;
+        mDataNetworkValidation.signalStrength = mPhone.getSignalStrength().getLevel();
+        mDataNetworkValidation.validationResult = TelephonyStatsLog
+                .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_UNSPECIFIED;
+
+        // Store.
+        mAtomsStorage.addDataNetworkValidation(mDataNetworkValidation);
+
+        // clear all values.
+        clear();
+    }
+
+    /**
+     * Store an atom by updated state.
+     *
+     * Called when the validation status is updated, and saves the atom when a failure or success
+     * result is received.
+     *
+     * @param status Data Network Validation Status.
+     * @param networkType Current Network Type of the Data Network.
+     */
+    public void onUpdateNetworkValidationState(
+            @PreciseDataConnectionState.NetworkValidationStatus int status,
+            @NetworkType int networkType) {
+        // Nothing to do, if never requested validation
+        if (mDataNetworkValidation == null) {
+            return;
+        }
+
+        switch (status) {
+            // Immediately after requesting validation, these messages may occur. In this case,
+            // ignore it and wait for the next update.
+            case PreciseDataConnectionState.NETWORK_VALIDATION_NOT_REQUESTED: // fall-through
+            case PreciseDataConnectionState.NETWORK_VALIDATION_IN_PROGRESS:
+                return;
+            // If status is unsupported, NetworkValidation should not be requested initially. logs
+            // this for abnormal tracking.
+            case PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED:
+                mDataNetworkValidation.validationResult = TelephonyStatsLog
+                    .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_NOT_SUPPORTED;
+                break;
+            // Success or failure corresponds to the result, store an atom.
+            case PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS:
+            case PreciseDataConnectionState.NETWORK_VALIDATION_FAILURE:
+                mDataNetworkValidation.validationResult = status;
+                break;
+        }
+
+        // Set data for and atom.
+        calcElapsedTime();
+        mDataNetworkValidation.networkType = networkType;
+        mDataNetworkValidation.signalStrength = mPhone.getSignalStrength().getLevel();
+
+        // Store.
+        mAtomsStorage.addDataNetworkValidation(mDataNetworkValidation);
+
+        // clear all values.
+        clear();
+    }
+
+    /**
+     * Calculate the current time required based on when network validation is requested.
+     */
+    private void calcElapsedTime() {
+        if (mDataNetworkValidation != null && mRequestedTimeInMillis != 0) {
+            mDataNetworkValidation.elapsedTimeInMillis = getTimeMillis() - mRequestedTimeInMillis;
+        }
+    }
+
+    /**
+     * Returns current time in millis from boot.
+     */
+    @VisibleForTesting
+    @ElapsedRealtimeLong
+    protected long getTimeMillis() {
+        return SystemClock.elapsedRealtime();
+    }
+
+    /**
+     * Clear all values.
+     */
+    private void clear() {
+        mDataNetworkValidation = null;
+        mRequestedTimeInMillis = 0;
+    }
+
+
+    /** Creates a DataNetworkValidation proto with default values. */
+    @NonNull
+    private DataNetworkValidation getDefaultProto(@ApnType int apnTypeBitmask) {
+        DataNetworkValidation proto = new DataNetworkValidation();
+        proto.networkType =
+                TelephonyStatsLog.DATA_NETWORK_VALIDATION__NETWORK_TYPE__NETWORK_TYPE_UNKNOWN;
+        proto.apnTypeBitmask = apnTypeBitmask;
+        proto.signalStrength =
+                TelephonyStatsLog
+                        .DATA_NETWORK_VALIDATION__SIGNAL_STRENGTH__SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+        proto.validationResult =
+                TelephonyStatsLog
+                        .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_UNSPECIFIED;
+        proto.elapsedTimeInMillis = 0;
+        proto.handoverAttempted = false;
+        proto.networkValidationCount = 1;
+        return proto;
+    }
+}
+
diff --git a/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java b/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java
index 29729c8..9ab52fb 100644
--- a/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java
+++ b/src/java/com/android/internal/telephony/metrics/DeviceStateHelper.java
@@ -41,7 +41,7 @@
                 .registerCallback(
                         new HandlerExecutor(new Handler(mHandlerThread.getLooper())),
                         state -> {
-                            updateFoldState(state);
+                            updateFoldState(state.getIdentifier());
                         });
     }
 
diff --git a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
index a315f1e..a83cd06 100644
--- a/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
+++ b/src/java/com/android/internal/telephony/metrics/MetricsCollector.java
@@ -17,9 +17,12 @@
 package com.android.internal.telephony.metrics;
 
 import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ID_TABLE_VERSION;
+import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS;
+import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ROAMING_SATELLITE_SESSION;
 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_DATA_SERVICE_SWITCH;
 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE;
 import static com.android.internal.telephony.TelephonyStatsLog.DATA_CALL_SESSION;
+import static com.android.internal.telephony.TelephonyStatsLog.DATA_NETWORK_VALIDATION;
 import static com.android.internal.telephony.TelephonyStatsLog.DEVICE_TELEPHONY_PROPERTIES;
 import static com.android.internal.telephony.TelephonyStatsLog.EMERGENCY_NUMBERS_INFO;
 import static com.android.internal.telephony.TelephonyStatsLog.GBA_EVENT;
@@ -36,7 +39,10 @@
 import static com.android.internal.telephony.TelephonyStatsLog.PRESENCE_NOTIFY_EVENT;
 import static com.android.internal.telephony.TelephonyStatsLog.RCS_ACS_PROVISIONING_STATS;
 import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_ACCESS_CONTROLLER;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_CONFIG_UPDATER;
 import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_CONTROLLER;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_ENTITLEMENT;
 import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_INCOMING_DATAGRAM;
 import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_OUTGOING_DATAGRAM;
 import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_PROVISION;
@@ -69,9 +75,12 @@
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteControllerStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteSession;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
+import com.android.internal.telephony.nano.PersistAtomsProto.DataNetworkValidation;
 import com.android.internal.telephony.nano.PersistAtomsProto.EmergencyNumbersInfo;
 import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent;
@@ -87,7 +96,10 @@
 import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteAccessController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
@@ -138,14 +150,6 @@
     private static final long MIN_COOLDOWN_MILLIS =
             DBG ? 10L * MILLIS_PER_SECOND : 23L * MILLIS_PER_HOUR;
 
-    /**
-     * Sets atom pull cool down to 4 minutes for userdebug build.
-     *
-     * <p>Applies to certain atoms: CellularServiceState.
-     */
-    private static final long CELL_SERVICE_MIN_COOLDOWN_MILLIS =
-            DBG ? 10L * MILLIS_PER_SECOND :
-                    IS_DEBUGGABLE ? 4L * MILLIS_PER_MINUTE : 23L * MILLIS_PER_HOUR;
 
     /**
      * Buckets with less than these many calls will be dropped.
@@ -166,6 +170,13 @@
     private static final long CELL_SERVICE_DURATION_BUCKET_MILLIS =
             DBG || IS_DEBUGGABLE ? 2L * MILLIS_PER_SECOND : 5L * MILLIS_PER_MINUTE;
 
+    /**
+     * Sets atom pull cool down to 4 minutes for userdebug build, 5 hours for user build.
+     *
+     * <p>Applies to certain atoms: CellularServiceState, DataCallSession,
+     * ImsRegistrationTermination.
+     */
+    private final long mPowerCorrelatedMinCooldownMillis;
     private final PersistAtomsStorage mStorage;
     private final DeviceStateHelper mDeviceStateHelper;
     private final StatsManager mStatsManager;
@@ -177,14 +188,15 @@
 
     public MetricsCollector(Context context, @NonNull FeatureFlags featureFlags) {
         this(context, new PersistAtomsStorage(context),
-                new DeviceStateHelper(context), new VonrHelper(featureFlags), featureFlags);
+                new DeviceStateHelper(context), new VonrHelper(featureFlags),
+                new DefaultNetworkMonitor(context, featureFlags), featureFlags);
     }
 
     /** Allows dependency injection. Used during unit tests. */
     @VisibleForTesting
-    public MetricsCollector(
-            Context context, PersistAtomsStorage storage, DeviceStateHelper deviceStateHelper,
-                    VonrHelper vonrHelper, @NonNull FeatureFlags featureFlags) {
+    public MetricsCollector(Context context, PersistAtomsStorage storage,
+            DeviceStateHelper deviceStateHelper, VonrHelper vonrHelper,
+            DefaultNetworkMonitor defaultNetworkMonitor, @NonNull FeatureFlags featureFlags) {
         mStorage = storage;
         mDeviceStateHelper = deviceStateHelper;
         mStatsManager = (StatsManager) context.getSystemService(Context.STATS_MANAGER);
@@ -227,6 +239,12 @@
             registerAtom(SATELLITE_OUTGOING_DATAGRAM);
             registerAtom(SATELLITE_PROVISION);
             registerAtom(SATELLITE_SOS_MESSAGE_RECOMMENDER);
+            registerAtom(DATA_NETWORK_VALIDATION);
+            registerAtom(CARRIER_ROAMING_SATELLITE_SESSION);
+            registerAtom(CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS);
+            registerAtom(SATELLITE_ENTITLEMENT);
+            registerAtom(SATELLITE_CONFIG_UPDATER);
+            registerAtom(SATELLITE_ACCESS_CONTROLLER);
             Rlog.d(TAG, "registered");
         } else {
             Rlog.e(TAG, "could not get StatsManager, atoms not registered");
@@ -234,6 +252,9 @@
 
         mAirplaneModeStats = new AirplaneModeStats(context);
         mDefaultNetworkMonitor = new DefaultNetworkMonitor(context, featureFlags);
+        mPowerCorrelatedMinCooldownMillis = DBG ? 10L * MILLIS_PER_SECOND :
+                IS_DEBUGGABLE ? 4L * MILLIS_PER_MINUTE : (long) context.getResources().getInteger(
+                com.android.internal.R.integer.config_metrics_pull_cooldown_millis);
     }
 
     /**
@@ -318,6 +339,18 @@
                 return pullSatelliteProvision(data);
             case SATELLITE_SOS_MESSAGE_RECOMMENDER:
                 return pullSatelliteSosMessageRecommender(data);
+            case DATA_NETWORK_VALIDATION:
+                return pullDataNetworkValidation(data);
+            case CARRIER_ROAMING_SATELLITE_SESSION:
+                return pullCarrierRoamingSatelliteSession(data);
+            case CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS:
+                return pullCarrierRoamingSatelliteControllerStats(data);
+            case SATELLITE_ENTITLEMENT:
+                return pullSatelliteEntitlement(data);
+            case SATELLITE_CONFIG_UPDATER:
+                return pullSatelliteConfigUpdater(data);
+            case SATELLITE_ACCESS_CONTROLLER:
+                return pullSatelliteAccessController(data);
             default:
                 Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag));
                 return StatsManager.PULL_SKIP;
@@ -516,7 +549,8 @@
     private int pullDataCallSession(List<StatsEvent> data) {
         // Include ongoing data call segments
         concludeDataCallSessionStats();
-        DataCallSession[] dataCallSessions = mStorage.getDataCallSessions(MIN_COOLDOWN_MILLIS);
+        DataCallSession[] dataCallSessions = mStorage.getDataCallSessions(
+                mPowerCorrelatedMinCooldownMillis);
         if (dataCallSessions != null) {
             Arrays.stream(dataCallSessions)
                     .forEach(dataCall -> data.add(buildStatsEvent(dataCall)));
@@ -544,8 +578,8 @@
     private int pullCellularServiceState(List<StatsEvent> data) {
         // Include the latest durations
         concludeServiceStateStats();
-        CellularServiceState[] persistAtoms =
-                mStorage.getCellularServiceStates(CELL_SERVICE_MIN_COOLDOWN_MILLIS);
+        CellularServiceState[] persistAtoms = mStorage.getCellularServiceStates(
+                mPowerCorrelatedMinCooldownMillis);
         if (persistAtoms != null) {
             // list is already shuffled when instances were inserted
             Arrays.stream(persistAtoms)
@@ -573,8 +607,8 @@
     }
 
     private int pullImsRegistrationTermination(List<StatsEvent> data) {
-        ImsRegistrationTermination[] persistAtoms =
-                mStorage.getImsRegistrationTerminations(MIN_COOLDOWN_MILLIS);
+        ImsRegistrationTermination[] persistAtoms = mStorage.getImsRegistrationTerminations(
+                mPowerCorrelatedMinCooldownMillis);
         if (persistAtoms != null) {
             // list is already shuffled when instances were inserted
             Arrays.stream(persistAtoms)
@@ -937,6 +971,85 @@
         }
     }
 
+    private int pullDataNetworkValidation(@NonNull List<StatsEvent> data) {
+        DataNetworkValidation[] dataNetworkValidations =
+                mStorage.getDataNetworkValidation(mPowerCorrelatedMinCooldownMillis);
+        if (dataNetworkValidations != null) {
+            Arrays.stream(dataNetworkValidations)
+                    .forEach(d -> data.add(buildStatsEvent(d)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "DATA_NETWORK_VALIDATION pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private int pullCarrierRoamingSatelliteSession(List<StatsEvent> data) {
+        CarrierRoamingSatelliteSession[] carrierRoamingSatelliteSessionAtoms =
+                mStorage.getCarrierRoamingSatelliteSessionStats(MIN_COOLDOWN_MILLIS);
+        if (carrierRoamingSatelliteSessionAtoms != null) {
+            Arrays.stream(carrierRoamingSatelliteSessionAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "CARRIER_ROAMING_SATELLITE_SESSION pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private int pullCarrierRoamingSatelliteControllerStats(List<StatsEvent> data) {
+        CarrierRoamingSatelliteControllerStats[] carrierRoamingSatelliteControllerStatsAtoms =
+                mStorage.getCarrierRoamingSatelliteControllerStats(MIN_COOLDOWN_MILLIS);
+        if (carrierRoamingSatelliteControllerStatsAtoms != null) {
+            Arrays.stream(carrierRoamingSatelliteControllerStatsAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS "
+                    + "pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private int pullSatelliteEntitlement(List<StatsEvent> data) {
+        SatelliteEntitlement[] satelliteEntitlementAtoms =
+                mStorage.getSatelliteEntitlementStats(MIN_COOLDOWN_MILLIS);
+        if (satelliteEntitlementAtoms != null) {
+            Arrays.stream(satelliteEntitlementAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "SATELLITE_ENTITLEMENT pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private int pullSatelliteConfigUpdater(List<StatsEvent> data) {
+        SatelliteConfigUpdater[] satelliteConfigUpdaterAtoms =
+                mStorage.getSatelliteConfigUpdaterStats(MIN_COOLDOWN_MILLIS);
+        if (satelliteConfigUpdaterAtoms != null) {
+            Arrays.stream(satelliteConfigUpdaterAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "SATELLITE_CONFIG_UPDATER pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private int pullSatelliteAccessController(List<StatsEvent> data) {
+        SatelliteAccessController[] satelliteAccessControllerAtoms =
+                mStorage.getSatelliteAccessControllerStats(MIN_COOLDOWN_MILLIS);
+        if (satelliteAccessControllerAtoms != null) {
+            Arrays.stream(satelliteAccessControllerAtoms)
+                    .forEach(persistAtom -> data.add(buildStatsEvent(persistAtom)));
+            return StatsManager.PULL_SUCCESS;
+        } else {
+            Rlog.w(TAG, "SATELLITE_ACCESS_CONTROLLER pull too frequent, skipping");
+            return StatsManager.PULL_SKIP;
+        }
+    }
+
     /** Registers a pulled atom ID {@code atomId}. */
     private void registerAtom(int atomId) {
         mStatsManager.setPullAtomCallback(atomId, /* metadata= */ null,
@@ -972,7 +1085,8 @@
                 state.foldState,
                 state.overrideVoiceService,
                 state.isDataEnabled,
-                state.isIwlanCrossSim);
+                state.isIwlanCrossSim,
+                state.isNtn);
     }
 
     private static StatsEvent buildStatsEvent(VoiceCallRatUsage usage) {
@@ -1030,7 +1144,10 @@
                 session.isIwlanCrossSimAtStart,
                 session.isIwlanCrossSimAtEnd,
                 session.isIwlanCrossSimAtConnected,
-                session.vonrEnabled);
+                session.vonrEnabled,
+                session.isNtn,
+                session.supportsBusinessCallComposer,
+                session.callComposerStatus);
 
     }
 
@@ -1052,7 +1169,9 @@
                 sms.carrierId,
                 sms.messageId,
                 sms.count,
-                sms.isManagedProfile);
+                sms.isManagedProfile,
+                sms.isNtn,
+                sms.isEmergency);
     }
 
     private static StatsEvent buildStatsEvent(OutgoingSms sms) {
@@ -1075,7 +1194,9 @@
                 sms.count,
                 sms.sendErrorCode,
                 sms.networkErrorCode,
-                sms.isManagedProfile);
+                sms.isManagedProfile,
+                sms.isEmergency,
+                sms.isNtn);
     }
 
     private static StatsEvent buildStatsEvent(DataCallSession dataCallSession) {
@@ -1104,7 +1225,10 @@
                 dataCallSession.handoverFailureCauses,
                 dataCallSession.handoverFailureRat,
                 dataCallSession.isNonDds,
-                dataCallSession.isIwlanCrossSim);
+                dataCallSession.isIwlanCrossSim,
+                dataCallSession.isNtn,
+                dataCallSession.isSatelliteTransport,
+                dataCallSession.isProvisioningProfile);
     }
 
     private static StatsEvent buildStatsEvent(ImsRegistrationStats stats) {
@@ -1339,7 +1463,18 @@
                 satelliteController.countOfDeprovisionFail,
                 satelliteController.totalServiceUptimeSec,
                 satelliteController.totalBatteryConsumptionPercent,
-                satelliteController.totalBatteryChargedTimeSec);
+                satelliteController.totalBatteryChargedTimeSec,
+                satelliteController.countOfDemoModeSatelliteServiceEnablementsSuccess,
+                satelliteController.countOfDemoModeSatelliteServiceEnablementsFail,
+                satelliteController.countOfDemoModeOutgoingDatagramSuccess,
+                satelliteController.countOfDemoModeOutgoingDatagramFail,
+                satelliteController.countOfDemoModeIncomingDatagramSuccess,
+                satelliteController.countOfDemoModeIncomingDatagramFail,
+                satelliteController.countOfDatagramTypeKeepAliveSuccess,
+                satelliteController.countOfDatagramTypeKeepAliveFail,
+                satelliteController.countOfAllowedSatelliteAccess,
+                satelliteController.countOfDisallowedSatelliteAccess,
+                satelliteController.countOfSatelliteAccessCheckFail);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteSession satelliteSession) {
@@ -1347,7 +1482,17 @@
                 SATELLITE_SESSION,
                 satelliteSession.satelliteServiceInitializationResult,
                 satelliteSession.satelliteTechnology,
-                satelliteSession.count);
+                satelliteSession.count,
+                satelliteSession.satelliteServiceTerminationResult,
+                satelliteSession.initializationProcessingTimeMillis,
+                satelliteSession.terminationProcessingTimeMillis,
+                satelliteSession.sessionDurationSeconds,
+                satelliteSession.countOfOutgoingDatagramSuccess,
+                satelliteSession.countOfOutgoingDatagramFailed,
+                satelliteSession.countOfIncomingDatagramSuccess,
+                satelliteSession.countOfIncomingDatagramFailed,
+                satelliteSession.isDemoMode,
+                satelliteSession.maxNtnSignalStrengthLevel);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteIncomingDatagram stats) {
@@ -1355,7 +1500,8 @@
                 SATELLITE_INCOMING_DATAGRAM,
                 stats.resultCode,
                 stats.datagramSizeBytes,
-                stats.datagramTransferTimeMillis);
+                stats.datagramTransferTimeMillis,
+                stats.isDemoMode);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteOutgoingDatagram stats) {
@@ -1364,7 +1510,8 @@
                 stats.datagramType,
                 stats.resultCode,
                 stats.datagramSizeBytes,
-                stats.datagramTransferTimeMillis);
+                stats.datagramTransferTimeMillis,
+                stats.isDemoMode);
     }
 
     private static StatsEvent buildStatsEvent(SatelliteProvision stats) {
@@ -1389,6 +1536,83 @@
                 stats.isSatelliteAllowedInCurrentLocation);
     }
 
+    private static StatsEvent buildStatsEvent(DataNetworkValidation stats) {
+        return TelephonyStatsLog.buildStatsEvent(
+                DATA_NETWORK_VALIDATION,
+                stats.networkType,
+                stats.apnTypeBitmask,
+                stats.signalStrength,
+                stats.validationResult,
+                stats.elapsedTimeInMillis,
+                stats.handoverAttempted,
+                stats.networkValidationCount);
+    }
+
+    private static StatsEvent buildStatsEvent(CarrierRoamingSatelliteSession stats) {
+        return TelephonyStatsLog.buildStatsEvent(
+                CARRIER_ROAMING_SATELLITE_SESSION,
+                stats.carrierId,
+                stats.isNtnRoamingInHomeCountry,
+                stats.totalSatelliteModeTimeSec,
+                stats.numberOfSatelliteConnections,
+                stats.avgDurationOfSatelliteConnectionSec,
+                stats.satelliteConnectionGapMinSec,
+                stats.satelliteConnectionGapAvgSec,
+                stats.satelliteConnectionGapMaxSec,
+                stats.rsrpAvg,
+                stats.rsrpMedian,
+                stats.rssnrAvg,
+                stats.rssnrMedian,
+                stats.countOfIncomingSms,
+                stats.countOfOutgoingSms,
+                stats.countOfIncomingMms,
+                stats.countOfOutgoingMms);
+    }
+
+    private static StatsEvent buildStatsEvent(CarrierRoamingSatelliteControllerStats stats) {
+        return TelephonyStatsLog.buildStatsEvent(
+                CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS,
+                stats.configDataSource,
+                stats.countOfEntitlementStatusQueryRequest,
+                stats.countOfSatelliteConfigUpdateRequest,
+                stats.countOfSatelliteNotificationDisplayed,
+                stats.satelliteSessionGapMinSec,
+                stats.satelliteSessionGapAvgSec,
+                stats.satelliteSessionGapMaxSec);
+    }
+
+    private static StatsEvent buildStatsEvent(SatelliteEntitlement stats) {
+        return TelephonyStatsLog.buildStatsEvent(
+                SATELLITE_ENTITLEMENT,
+                stats.carrierId,
+                stats.result,
+                stats.entitlementStatus,
+                stats.isRetry,
+                stats.count);
+    }
+
+    private static StatsEvent buildStatsEvent(SatelliteConfigUpdater stats) {
+        return TelephonyStatsLog.buildStatsEvent(SATELLITE_CONFIG_UPDATER,
+                stats.configVersion,
+                stats.oemConfigResult,
+                stats.carrierConfigResult,
+                stats.count);
+    }
+
+    private static StatsEvent buildStatsEvent(SatelliteAccessController stats) {
+        return TelephonyStatsLog.buildStatsEvent(
+                SATELLITE_ACCESS_CONTROLLER,
+                stats.accessControlType,
+                stats.locationQueryTimeMillis,
+                stats.onDeviceLookupTimeMillis,
+                stats.totalCheckingTimeMillis,
+                stats.isAllowed,
+                stats.isEmergency,
+                stats.resultCode,
+                stats.countryCodes,
+                stats.configDataSource);
+    }
+
     /** Returns all phones in {@link PhoneFactory}, or an empty array if phones not made yet. */
     static Phone[] getPhonesIfAny() {
         try {
diff --git a/src/java/com/android/internal/telephony/metrics/NetworkRequestsStats.java b/src/java/com/android/internal/telephony/metrics/NetworkRequestsStats.java
index 26c28f0..6f4a1a0 100644
--- a/src/java/com/android/internal/telephony/metrics/NetworkRequestsStats.java
+++ b/src/java/com/android/internal/telephony/metrics/NetworkRequestsStats.java
@@ -59,6 +59,47 @@
             networkRequestsTemplate.capability = NetworkRequestsV2.NetworkCapability.ENTERPRISE;
             storage.addNetworkRequestsV2(networkRequestsTemplate);
         }
+
+        if (networkRequest.hasTransport(NetworkCapabilities.TRANSPORT_SATELLITE)
+                && !networkRequest.hasCapability(
+                        NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) {
+
+            if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+                networkRequestsTemplate.capability =
+                        NetworkRequestsV2.NetworkCapability.SATELLITE_INTERNET_RESTRICTED;
+                storage.addNetworkRequestsV2(networkRequestsTemplate);
+            }
+
+            if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+                networkRequestsTemplate.capability =
+                        NetworkRequestsV2.NetworkCapability.SATELLITE_MMS_RESTRICTED;
+                storage.addNetworkRequestsV2(networkRequestsTemplate);
+            }
+
+            if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+                networkRequestsTemplate.capability =
+                        NetworkRequestsV2.NetworkCapability.SATELLITE_IMS_RESTRICTED;
+                storage.addNetworkRequestsV2(networkRequestsTemplate);
+            }
+
+            if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_XCAP)) {
+                networkRequestsTemplate.capability =
+                        NetworkRequestsV2.NetworkCapability.SATELLITE_XCAP_RESTRICTED;
+                storage.addNetworkRequestsV2(networkRequestsTemplate);
+            }
+
+            if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)) {
+                networkRequestsTemplate.capability =
+                        NetworkRequestsV2.NetworkCapability.SATELLITE_EIMS_RESTRICTED;
+                storage.addNetworkRequestsV2(networkRequestsTemplate);
+            }
+
+            if (networkRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+                networkRequestsTemplate.capability =
+                        NetworkRequestsV2.NetworkCapability.SATELLITE_SUPL_RESTRICTED;
+                storage.addNetworkRequestsV2(networkRequestsTemplate);
+            }
+        }
     }
 
     /** Returns the carrier ID of the given subscription id. */
diff --git a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
index f3fe8fa..12dab7a 100644
--- a/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
+++ b/src/java/com/android/internal/telephony/metrics/PersistAtomsStorage.java
@@ -30,9 +30,12 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.nano.PersistAtomsProto.CarrierIdMismatch;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteControllerStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteSession;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
+import com.android.internal.telephony.nano.PersistAtomsProto.DataNetworkValidation;
 import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerListenerEvent;
@@ -48,7 +51,10 @@
 import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteAccessController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
@@ -172,6 +178,10 @@
     /** Maximum number of Satellite relevant stats to store between pulls. */
     private final int mMaxNumSatelliteStats;
     private final int mMaxNumSatelliteControllerStats = 1;
+    private final int mMaxNumCarrierRoamingSatelliteSessionStats = 1;
+
+    /** Maximum number of data network validation to store during pulls. */
+    private final int mMaxNumDataNetworkValidation;
 
     /** Stores persist atoms and persist states of the puller. */
     @VisibleForTesting protected PersistAtoms mAtoms;
@@ -223,6 +233,7 @@
             mMaxNumGbaEventStats = 5;
             mMaxOutgoingShortCodeSms = 5;
             mMaxNumSatelliteStats = 5;
+            mMaxNumDataNetworkValidation = 5;
         } else {
             mMaxNumVoiceCallSessions = 50;
             mMaxNumSms = 25;
@@ -247,6 +258,7 @@
             mMaxNumGbaEventStats = 10;
             mMaxOutgoingShortCodeSms = 10;
             mMaxNumSatelliteStats = 15;
+            mMaxNumDataNetworkValidation = 15;
         }
 
         mAtoms = loadAtomsFromFile();
@@ -739,6 +751,25 @@
                 += stats.totalBatteryConsumptionPercent;
         atom.totalBatteryChargedTimeSec
                 += stats.totalBatteryChargedTimeSec;
+        atom.countOfDemoModeSatelliteServiceEnablementsSuccess
+                += stats.countOfDemoModeSatelliteServiceEnablementsSuccess;
+        atom.countOfDemoModeSatelliteServiceEnablementsFail
+                += stats.countOfDemoModeSatelliteServiceEnablementsFail;
+        atom.countOfDemoModeOutgoingDatagramSuccess
+                += stats.countOfDemoModeOutgoingDatagramSuccess;
+        atom.countOfDemoModeOutgoingDatagramFail
+                += stats.countOfDemoModeOutgoingDatagramFail;
+        atom.countOfDemoModeIncomingDatagramSuccess
+                += stats.countOfDemoModeIncomingDatagramSuccess;
+        atom.countOfDemoModeIncomingDatagramFail
+                += stats.countOfDemoModeIncomingDatagramFail;
+        atom.countOfDatagramTypeKeepAliveSuccess
+                += stats.countOfDatagramTypeKeepAliveSuccess;
+        atom.countOfDatagramTypeKeepAliveFail
+                += stats.countOfDatagramTypeKeepAliveFail;
+        atom.countOfAllowedSatelliteAccess += stats.countOfAllowedSatelliteAccess;
+        atom.countOfDisallowedSatelliteAccess += stats.countOfDisallowedSatelliteAccess;
+        atom.countOfSatelliteAccessCheckFail += stats.countOfSatelliteAccessCheckFail;
 
         mAtoms.satelliteController = atomArray;
         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
@@ -791,6 +822,90 @@
         saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
     }
 
+    /** Adds a data network validation to the storage. */
+    public synchronized void addDataNetworkValidation(DataNetworkValidation dataNetworkValidation) {
+        DataNetworkValidation existingStats = find(dataNetworkValidation);
+        if (existingStats != null) {
+            int count = existingStats.networkValidationCount
+                    + dataNetworkValidation.networkValidationCount;
+            long elapsedTime = ((dataNetworkValidation.elapsedTimeInMillis
+                    * dataNetworkValidation.networkValidationCount) + (
+                    existingStats.elapsedTimeInMillis * existingStats.networkValidationCount))
+                    / count;
+            existingStats.networkValidationCount = count;
+            existingStats.elapsedTimeInMillis = elapsedTime;
+        } else {
+            mAtoms.dataNetworkValidation = insertAtRandomPlace(
+                    mAtoms.dataNetworkValidation, dataNetworkValidation, mMaxNumDataCallSessions);
+        }
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
+    /** Adds a new {@link CarrierRoamingSatelliteSession} to the storage. */
+    public synchronized void addCarrierRoamingSatelliteSessionStats(
+            CarrierRoamingSatelliteSession stats) {
+        mAtoms.carrierRoamingSatelliteSession = insertAtRandomPlace(
+                mAtoms.carrierRoamingSatelliteSession, stats,
+                mMaxNumCarrierRoamingSatelliteSessionStats);
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
+    /** 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 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);
+    }
+
+    /** Adds a new {@link SatelliteEntitlement} to the storage. */
+    public synchronized void addSatelliteEntitlementStats(SatelliteEntitlement stats) {
+        SatelliteEntitlement existingStats = find(stats);
+        if (existingStats != null) {
+            existingStats.count += 1;
+        } else {
+            mAtoms.satelliteEntitlement = insertAtRandomPlace(mAtoms.satelliteEntitlement,
+                    stats, mMaxNumSatelliteStats);
+        }
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
+    /** Adds a new {@link SatelliteConfigUpdater} to the storage. */
+    public synchronized void addSatelliteConfigUpdaterStats(SatelliteConfigUpdater stats) {
+        SatelliteConfigUpdater existingStats = find(stats);
+        if (existingStats != null) {
+            existingStats.count += 1;
+        } else {
+            mAtoms.satelliteConfigUpdater = insertAtRandomPlace(mAtoms.satelliteConfigUpdater,
+                    stats, mMaxNumSatelliteStats);
+        }
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
+    /** Adds a new {@link SatelliteAccessController} to the storage. */
+    public synchronized void addSatelliteAccessControllerStats(SatelliteAccessController stats) {
+        mAtoms.satelliteAccessController =
+                insertAtRandomPlace(mAtoms.satelliteAccessController, stats,
+                        mMaxNumSatelliteStats);
+        saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_UPDATE_MILLIS);
+    }
+
     /**
      * Returns and clears the voice call sessions if last pulled longer than {@code
      * minIntervalMillis} ago, otherwise returns {@code null}.
@@ -1439,7 +1554,7 @@
             long minIntervalMillis) {
         if (getWallTimeMillis() - mAtoms.satelliteSosMessageRecommenderPullTimestampMillis
                 > minIntervalMillis) {
-            mAtoms.satelliteProvisionPullTimestampMillis = getWallTimeMillis();
+            mAtoms.satelliteSosMessageRecommenderPullTimestampMillis = getWallTimeMillis();
             SatelliteSosMessageRecommender[] statsArray = mAtoms.satelliteSosMessageRecommender;
             mAtoms.satelliteSosMessageRecommender = new SatelliteSosMessageRecommender[0];
             saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
@@ -1449,6 +1564,121 @@
         }
     }
 
+    /**
+     * Returns and clears the data network validation if last pulled longer than {@code
+     * minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized DataNetworkValidation[] getDataNetworkValidation(long minIntervalMillis) {
+        long wallTime = getWallTimeMillis();
+        if (wallTime - mAtoms.dataNetworkValidationPullTimestampMillis > minIntervalMillis) {
+            mAtoms.dataNetworkValidationPullTimestampMillis = wallTime;
+            DataNetworkValidation[] previousDataNetworkValidation = mAtoms.dataNetworkValidation;
+            mAtoms.dataNetworkValidation = new DataNetworkValidation[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return previousDataNetworkValidation;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the {@link CarrierRoamingSatelliteSession} stats if last pulled
+     * longer than {@code minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized CarrierRoamingSatelliteSession[] getCarrierRoamingSatelliteSessionStats(
+            long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.carrierRoamingSatelliteSessionPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.carrierRoamingSatelliteSessionPullTimestampMillis = getWallTimeMillis();
+            CarrierRoamingSatelliteSession[] statsArray = mAtoms.carrierRoamingSatelliteSession;
+            mAtoms.carrierRoamingSatelliteSession = new CarrierRoamingSatelliteSession[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the {@link CarrierRoamingSatelliteControllerStats} stats if last pulled
+     * longer than {@code minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized CarrierRoamingSatelliteControllerStats[]
+            getCarrierRoamingSatelliteControllerStats(long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis = getWallTimeMillis();
+            CarrierRoamingSatelliteControllerStats[] statsArray =
+                    mAtoms.carrierRoamingSatelliteControllerStats;
+            mAtoms.carrierRoamingSatelliteControllerStats =
+                    new CarrierRoamingSatelliteControllerStats[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the {@link SatelliteEntitlement} stats if last pulled longer than {@code
+     * minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized SatelliteEntitlement[] getSatelliteEntitlementStats(
+            long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.satelliteEntitlementPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.satelliteEntitlementPullTimestampMillis = getWallTimeMillis();
+            SatelliteEntitlement[] statsArray = mAtoms.satelliteEntitlement;
+            mAtoms.satelliteEntitlement = new SatelliteEntitlement[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the {@link SatelliteConfigUpdater} stats if last pulled longer than {@code
+     * minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized SatelliteConfigUpdater[] getSatelliteConfigUpdaterStats(
+            long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.satelliteConfigUpdaterPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.satelliteConfigUpdaterPullTimestampMillis = getWallTimeMillis();
+            SatelliteConfigUpdater[] statsArray = mAtoms.satelliteConfigUpdater;
+            mAtoms.satelliteConfigUpdater = new SatelliteConfigUpdater[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns and clears the {@link SatelliteAccessController} stats if last pulled longer
+     * than {@code minIntervalMillis} ago, otherwise returns {@code null}.
+     */
+    @Nullable
+    public synchronized SatelliteAccessController[] getSatelliteAccessControllerStats(
+            long minIntervalMillis) {
+        if (getWallTimeMillis() - mAtoms.satelliteAccessControllerPullTimestampMillis
+                > minIntervalMillis) {
+            mAtoms.satelliteAccessControllerPullTimestampMillis = getWallTimeMillis();
+            SatelliteAccessController[] statsArray = mAtoms.satelliteAccessController;
+            mAtoms.satelliteAccessController = new SatelliteAccessController[0];
+            saveAtomsToFile(SAVE_TO_FILE_DELAY_FOR_GET_MILLIS);
+            return statsArray;
+        } else {
+            return null;
+        }
+    }
+
     /** Saves {@link PersistAtoms} to a file in private storage immediately. */
     public synchronized void flushAtoms() {
         saveAtomsToFile(0);
@@ -1599,6 +1829,25 @@
             atoms.satelliteSosMessageRecommender = sanitizeAtoms(
                     atoms.satelliteSosMessageRecommender, SatelliteSosMessageRecommender.class,
                     mMaxNumSatelliteStats);
+            atoms.dataNetworkValidation =
+                    sanitizeAtoms(
+                            atoms.dataNetworkValidation,
+                            DataNetworkValidation.class,
+                            mMaxNumDataNetworkValidation
+                    );
+            atoms.carrierRoamingSatelliteSession = sanitizeAtoms(
+                    atoms.carrierRoamingSatelliteSession, CarrierRoamingSatelliteSession.class,
+                    mMaxNumSatelliteStats);
+            atoms.carrierRoamingSatelliteControllerStats = sanitizeAtoms(
+                    atoms.carrierRoamingSatelliteControllerStats,
+                    CarrierRoamingSatelliteControllerStats.class, mMaxNumSatelliteControllerStats);
+            atoms.satelliteEntitlement = sanitizeAtoms(atoms.satelliteEntitlement,
+                    SatelliteEntitlement.class, mMaxNumSatelliteStats);
+            atoms.satelliteConfigUpdater = sanitizeAtoms(atoms.satelliteConfigUpdater,
+                    SatelliteConfigUpdater.class, mMaxNumSatelliteStats);
+            atoms.satelliteAccessController = sanitizeAtoms(
+                    atoms.satelliteAccessController, SatelliteAccessController.class,
+                    mMaxNumSatelliteStats);
 
             // out of caution, sanitize also the timestamps
             atoms.voiceCallRatUsagePullTimestampMillis =
@@ -1661,6 +1910,18 @@
                     sanitizeTimestamp(atoms.satelliteProvisionPullTimestampMillis);
             atoms.satelliteSosMessageRecommenderPullTimestampMillis =
                     sanitizeTimestamp(atoms.satelliteSosMessageRecommenderPullTimestampMillis);
+            atoms.dataNetworkValidationPullTimestampMillis =
+                    sanitizeTimestamp(atoms.dataNetworkValidationPullTimestampMillis);
+            atoms.carrierRoamingSatelliteSessionPullTimestampMillis = sanitizeTimestamp(
+                    atoms.carrierRoamingSatelliteSessionPullTimestampMillis);
+            atoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis = sanitizeTimestamp(
+                    atoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis);
+            atoms.satelliteEntitlementPullTimestampMillis =
+                    sanitizeTimestamp(atoms.satelliteEntitlementPullTimestampMillis);
+            atoms.satelliteConfigUpdaterPullTimestampMillis =
+                    sanitizeTimestamp(atoms.satelliteConfigUpdaterPullTimestampMillis);
+            atoms.satelliteAccessControllerPullTimestampMillis =
+                    sanitizeTimestamp(atoms.satelliteAccessControllerPullTimestampMillis);
             return atoms;
         } catch (NoSuchFileException e) {
             Rlog.d(TAG, "PersistAtoms file not found");
@@ -1715,7 +1976,8 @@
                     && state.foldState == key.foldState
                     && state.overrideVoiceService == key.overrideVoiceService
                     && state.isDataEnabled == key.isDataEnabled
-                    && state.isIwlanCrossSim == key.isIwlanCrossSim) {
+                    && state.isIwlanCrossSim == key.isIwlanCrossSim
+                    && state.isNtn == key.isNtn) {
                 return state;
             }
         }
@@ -2049,7 +2311,7 @@
     }
 
     /**
-     * Returns SatelliteOutgoingDatagram atom that has same values or {@code null}
+     * Returns SatelliteSession atom that has same values or {@code null}
      * if it does not exist.
      */
     private @Nullable SatelliteSession find(
@@ -2057,7 +2319,19 @@
         for (SatelliteSession stats : mAtoms.satelliteSession) {
             if (stats.satelliteServiceInitializationResult
                     == key.satelliteServiceInitializationResult
-                    && stats.satelliteTechnology == key.satelliteTechnology) {
+                    && stats.satelliteTechnology == key.satelliteTechnology
+                    && stats.satelliteServiceTerminationResult
+                    == key.satelliteServiceTerminationResult
+                    && stats.initializationProcessingTimeMillis
+                    == key.initializationProcessingTimeMillis
+                    && stats.terminationProcessingTimeMillis == key.terminationProcessingTimeMillis
+                    && stats.sessionDurationSeconds == key.sessionDurationSeconds
+                    && stats.countOfOutgoingDatagramSuccess == key.countOfOutgoingDatagramSuccess
+                    && stats.countOfOutgoingDatagramFailed == key.countOfOutgoingDatagramFailed
+                    && stats.countOfIncomingDatagramSuccess == key.countOfIncomingDatagramSuccess
+                    && stats.countOfIncomingDatagramFailed == key.countOfIncomingDatagramFailed
+                    && stats.isDemoMode == key.isDemoMode
+                    && stats.maxNtnSignalStrengthLevel == key.maxNtnSignalStrengthLevel) {
                 return stats;
             }
         }
@@ -2065,7 +2339,7 @@
     }
 
     /**
-     * Returns SatelliteOutgoingDatagram atom that has same values or {@code null}
+     * Returns SatelliteSosMessageRecommender atom that has same values or {@code null}
      * if it does not exist.
      */
     private @Nullable SatelliteSosMessageRecommender find(
@@ -2084,6 +2358,53 @@
     }
 
     /**
+     * Returns SatelliteOutgoingDatagram atom that has same values or {@code null}
+     * if it does not exist.
+     */
+    private @Nullable DataNetworkValidation find(DataNetworkValidation key) {
+        for (DataNetworkValidation stats : mAtoms.dataNetworkValidation) {
+            if (stats.networkType == key.networkType
+                    && stats.apnTypeBitmask == key.apnTypeBitmask
+                    && stats.signalStrength == key.signalStrength
+                    && stats.validationResult == key.validationResult
+                    && stats.handoverAttempted == key.handoverAttempted) {
+                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) {
+        for (SatelliteEntitlement stats : mAtoms.satelliteEntitlement) {
+            if (stats.carrierId == key.carrierId
+                    && stats.result == key.result
+                    && stats.entitlementStatus == key.entitlementStatus
+                    && stats.isRetry == key.isRetry) {
+                return stats;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns SatelliteConfigUpdater atom that has same values
+     * or {@code null} if it does not exist.
+     */
+    private @Nullable SatelliteConfigUpdater find(SatelliteConfigUpdater key) {
+        for (SatelliteConfigUpdater stats : mAtoms.satelliteConfigUpdater) {
+            if (stats.configVersion == key.configVersion
+                    && stats.oemConfigResult == key.oemConfigResult
+                    && stats.carrierConfigResult == key.carrierConfigResult) {
+                return stats;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Inserts a new element in a random position in an array with a maximum size.
      *
      * <p>If the array is full, merge with existing item if possible or replace one item randomly.
@@ -2339,6 +2660,12 @@
         atoms.satelliteOutgoingDatagramPullTimestampMillis = currentTime;
         atoms.satelliteProvisionPullTimestampMillis = currentTime;
         atoms.satelliteSosMessageRecommenderPullTimestampMillis = currentTime;
+        atoms.dataNetworkValidationPullTimestampMillis = currentTime;
+        atoms.carrierRoamingSatelliteSessionPullTimestampMillis = currentTime;
+        atoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis = currentTime;
+        atoms.satelliteEntitlementPullTimestampMillis = currentTime;
+        atoms.satelliteConfigUpdaterPullTimestampMillis = currentTime;
+        atoms.satelliteAccessControllerPullTimestampMillis = currentTime;
 
         Rlog.d(TAG, "created new PersistAtoms");
         return atoms;
diff --git a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
index 55eee1a..c2b2753 100644
--- a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
+++ b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
@@ -16,15 +16,28 @@
 
 package com.android.internal.telephony.metrics;
 
+import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE;
+
+import android.telephony.satellite.NtnSignalStrength;
+import android.telephony.satellite.SatelliteManager;
+
 import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteControllerStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteSession;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteAccessController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSession;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteSosMessageRecommender;
+import com.android.internal.telephony.satellite.SatelliteConstants;
 import com.android.telephony.Rlog;
 
+import java.util.Arrays;
+
 /** Tracks Satellite metrics for each phone */
 public class SatelliteStats {
     private static final String TAG = SatelliteStats.class.getSimpleName();
@@ -67,6 +80,17 @@
         private final int mTotalServiceUptimeSec;
         private final int mTotalBatteryConsumptionPercent;
         private final int mTotalBatteryChargedTimeSec;
+        private final int mCountOfDemoModeSatelliteServiceEnablementsSuccess;
+        private final int mCountOfDemoModeSatelliteServiceEnablementsFail;
+        private final int mCountOfDemoModeOutgoingDatagramSuccess;
+        private final int mCountOfDemoModeOutgoingDatagramFail;
+        private final int mCountOfDemoModeIncomingDatagramSuccess;
+        private final int mCountOfDemoModeIncomingDatagramFail;
+        private final int mCountOfDatagramTypeKeepAliveSuccess;
+        private final int mCountOfDatagramTypeKeepAliveFail;
+        private final int mCountOfAllowedSatelliteAccess;
+        private final int mCountOfDisallowedSatelliteAccess;
+        private final int mCountOfSatelliteAccessCheckFail;
 
         private SatelliteControllerParams(Builder builder) {
             this.mCountOfSatelliteServiceEnablementsSuccess =
@@ -90,6 +114,28 @@
             this.mTotalServiceUptimeSec = builder.mTotalServiceUptimeSec;
             this.mTotalBatteryConsumptionPercent = builder.mTotalBatteryConsumptionPercent;
             this.mTotalBatteryChargedTimeSec = builder.mTotalBatteryChargedTimeSec;
+            this.mCountOfDemoModeSatelliteServiceEnablementsSuccess =
+                    builder.mCountOfDemoModeSatelliteServiceEnablementsSuccess;
+            this.mCountOfDemoModeSatelliteServiceEnablementsFail =
+                    builder.mCountOfDemoModeSatelliteServiceEnablementsFail;
+            this.mCountOfDemoModeOutgoingDatagramSuccess =
+                    builder.mCountOfDemoModeOutgoingDatagramSuccess;
+            this.mCountOfDemoModeOutgoingDatagramFail =
+                    builder.mCountOfDemoModeOutgoingDatagramFail;
+            this.mCountOfDemoModeIncomingDatagramSuccess =
+                    builder.mCountOfDemoModeIncomingDatagramSuccess;
+            this.mCountOfDemoModeIncomingDatagramFail =
+                    builder.mCountOfDemoModeIncomingDatagramFail;
+            this.mCountOfDatagramTypeKeepAliveSuccess =
+                    builder.mCountOfDatagramTypeKeepAliveSuccess;
+            this.mCountOfDatagramTypeKeepAliveFail =
+                    builder.mCountOfDatagramTypeKeepAliveFail;
+            this.mCountOfAllowedSatelliteAccess =
+                    builder.mCountOfAllowedSatelliteAccess;
+            this.mCountOfDisallowedSatelliteAccess =
+                    builder.mCountOfDisallowedSatelliteAccess;
+            this.mCountOfSatelliteAccessCheckFail =
+                    builder.mCountOfSatelliteAccessCheckFail;
         }
 
         public int getCountOfSatelliteServiceEnablementsSuccess() {
@@ -160,6 +206,50 @@
             return mTotalBatteryChargedTimeSec;
         }
 
+        public int getCountOfDemoModeSatelliteServiceEnablementsSuccess() {
+            return mCountOfDemoModeSatelliteServiceEnablementsSuccess;
+        }
+
+        public int getCountOfDemoModeSatelliteServiceEnablementsFail() {
+            return mCountOfDemoModeSatelliteServiceEnablementsFail;
+        }
+
+        public int getCountOfDemoModeOutgoingDatagramSuccess() {
+            return mCountOfDemoModeOutgoingDatagramSuccess;
+        }
+
+        public int getCountOfDemoModeOutgoingDatagramFail() {
+            return mCountOfDemoModeOutgoingDatagramFail;
+        }
+
+        public int getCountOfDemoModeIncomingDatagramSuccess() {
+            return mCountOfDemoModeIncomingDatagramSuccess;
+        }
+
+        public int getCountOfDemoModeIncomingDatagramFail() {
+            return mCountOfDemoModeIncomingDatagramFail;
+        }
+
+        public int getCountOfDatagramTypeKeepAliveSuccess() {
+            return mCountOfDatagramTypeKeepAliveSuccess;
+        }
+
+        public int getCountOfDatagramTypeKeepAliveFail() {
+            return mCountOfDatagramTypeKeepAliveFail;
+        }
+
+        public int getCountOfAllowedSatelliteAccess() {
+            return mCountOfAllowedSatelliteAccess;
+        }
+
+        public int getCountOfDisallowedSatelliteAccess() {
+            return mCountOfDisallowedSatelliteAccess;
+        }
+
+        public int getCountOfSatelliteAccessCheckFail() {
+            return mCountOfSatelliteAccessCheckFail;
+        }
+
         /**
          * A builder class to create {@link SatelliteControllerParams} data structure class
          */
@@ -181,6 +271,17 @@
             private int mTotalServiceUptimeSec = 0;
             private int mTotalBatteryConsumptionPercent = 0;
             private int mTotalBatteryChargedTimeSec = 0;
+            private int mCountOfDemoModeSatelliteServiceEnablementsSuccess = 0;
+            private int mCountOfDemoModeSatelliteServiceEnablementsFail = 0;
+            private int mCountOfDemoModeOutgoingDatagramSuccess = 0;
+            private int mCountOfDemoModeOutgoingDatagramFail = 0;
+            private int mCountOfDemoModeIncomingDatagramSuccess = 0;
+            private int mCountOfDemoModeIncomingDatagramFail = 0;
+            private int mCountOfDatagramTypeKeepAliveSuccess = 0;
+            private int mCountOfDatagramTypeKeepAliveFail = 0;
+            private int mCountOfAllowedSatelliteAccess = 0;
+            private int mCountOfDisallowedSatelliteAccess = 0;
+            private int mCountOfSatelliteAccessCheckFail = 0;
 
             /**
              * Sets countOfSatelliteServiceEnablementsSuccess value of {@link SatelliteController}
@@ -345,6 +446,121 @@
             }
 
             /**
+             * Sets countOfDemoModeSatelliteServiceEnablementsSuccess value of
+             * {@link SatelliteController} atom then returns Builder class
+             */
+            public Builder setCountOfDemoModeSatelliteServiceEnablementsSuccess(
+                    int countOfDemoModeSatelliteServiceEnablementsSuccess) {
+                this.mCountOfDemoModeSatelliteServiceEnablementsSuccess =
+                        countOfDemoModeSatelliteServiceEnablementsSuccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfDemoModeSatelliteServiceEnablementsFail value of
+             * {@link SatelliteController} atom then returns Builder class
+             */
+            public Builder setCountOfDemoModeSatelliteServiceEnablementsFail(
+                    int countOfDemoModeSatelliteServiceEnablementsFail) {
+                this.mCountOfDemoModeSatelliteServiceEnablementsFail =
+                        countOfDemoModeSatelliteServiceEnablementsFail;
+                return this;
+            }
+
+            /**
+             * Sets countOfDemoModeOutgoingDatagramSuccess value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfDemoModeOutgoingDatagramSuccess(
+                    int countOfDemoModeOutgoingDatagramSuccess) {
+                this.mCountOfDemoModeOutgoingDatagramSuccess =
+                        countOfDemoModeOutgoingDatagramSuccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfDemoModeOutgoingDatagramFail value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfDemoModeOutgoingDatagramFail(
+                    int countOfDemoModeOutgoingDatagramFail) {
+                this.mCountOfDemoModeOutgoingDatagramFail = countOfDemoModeOutgoingDatagramFail;
+                return this;
+            }
+
+            /**
+             * Sets countOfDemoModeIncomingDatagramSuccess value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfDemoModeIncomingDatagramSuccess(
+                    int countOfDemoModeIncomingDatagramSuccess) {
+                this.mCountOfDemoModeIncomingDatagramSuccess =
+                        countOfDemoModeIncomingDatagramSuccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfDemoModeIncomingDatagramFail value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfDemoModeIncomingDatagramFail(
+                    int countOfDemoModeIncomingDatagramFail) {
+                this.mCountOfDemoModeIncomingDatagramFail = countOfDemoModeIncomingDatagramFail;
+                return this;
+            }
+
+            /**
+             * Sets countOfDatagramTypeKeepAliveSuccess value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfDatagramTypeKeepAliveSuccess(
+                    int countOfDatagramTypeKeepAliveSuccess) {
+                this.mCountOfDatagramTypeKeepAliveSuccess = countOfDatagramTypeKeepAliveSuccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfDatagramTypeKeepAliveFail value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfDatagramTypeKeepAliveFail(
+                    int countOfDatagramTypeKeepAliveFail) {
+                this.mCountOfDatagramTypeKeepAliveFail = countOfDatagramTypeKeepAliveFail;
+                return this;
+            }
+
+            /**
+             * Sets countOfAllowedSatelliteAccess value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfAllowedSatelliteAccess(
+                    int countOfAllowedSatelliteAccess) {
+                this.mCountOfAllowedSatelliteAccess =
+                        countOfAllowedSatelliteAccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfDisallowedSatelliteAccess value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfDisallowedSatelliteAccess(
+                    int countOfDisallowedSatelliteAccess) {
+                this.mCountOfDisallowedSatelliteAccess = countOfDisallowedSatelliteAccess;
+                return this;
+            }
+
+            /**
+             * Sets countOfSatelliteAccessCheckFail value of {@link SatelliteController} atom
+             * then returns Builder class
+             */
+            public Builder setCountOfSatelliteAccessCheckFail(
+                    int countOfSatelliteAccessCheckFail) {
+                this.mCountOfSatelliteAccessCheckFail = countOfSatelliteAccessCheckFail;
+                return this;
+            }
+
+            /**
              * Returns ControllerParams, which contains whole component of
              * {@link SatelliteController} atom
              */
@@ -374,6 +590,25 @@
                     + ", serviceUptimeSec=" + mTotalServiceUptimeSec
                     + ", batteryConsumptionPercent=" + mTotalBatteryConsumptionPercent
                     + ", batteryChargedTimeSec=" + mTotalBatteryChargedTimeSec
+                    + ", countOfDemoModeSatelliteServiceEnablementsSuccess="
+                    + mCountOfDemoModeSatelliteServiceEnablementsSuccess
+                    + ", countOfDemoModeSatelliteServiceEnablementsFail="
+                    + mCountOfDemoModeSatelliteServiceEnablementsFail
+                    + ", countOfDemoModeOutgoingDatagramSuccess="
+                    + mCountOfDemoModeOutgoingDatagramSuccess
+                    + ", countOfDemoModeOutgoingDatagramFail="
+                    + mCountOfDemoModeOutgoingDatagramFail
+                    + ", countOfDemoModeIncomingDatagramSuccess="
+                    + mCountOfDemoModeIncomingDatagramSuccess
+                    + ", countOfDemoModeIncomingDatagramFail="
+                    + mCountOfDemoModeIncomingDatagramFail
+                    + ", countOfDatagramTypeKeepAliveSuccess="
+                    + mCountOfDatagramTypeKeepAliveSuccess
+                    + ", countOfDatagramTypeKeepAliveFail="
+                    + mCountOfDatagramTypeKeepAliveFail
+                    + ", countOfAllowedSatelliteAccess=" + mCountOfAllowedSatelliteAccess
+                    + ", countOfDisallowedSatelliteAccess=" + mCountOfDisallowedSatelliteAccess
+                    + ", countOfSatelliteAccessCheckFail=" + mCountOfSatelliteAccessCheckFail
                     + ")";
         }
     }
@@ -385,11 +620,32 @@
     public class SatelliteSessionParams {
         private final int mSatelliteServiceInitializationResult;
         private final int mSatelliteTechnology;
+        private final int mTerminationResult;
+        private final long mInitializationProcessingTimeMillis;
+        private final long mTerminationProcessingTimeMillis;
+        private final int mSessionDurationSec;
+        private final int mCountOfOutgoingDatagramSuccess;
+        private final int mCountOfOutgoingDatagramFailed;
+        private final int mCountOfIncomingDatagramSuccess;
+        private final int mCountOfIncomingDatagramFailed;
+        private final boolean mIsDemoMode;
+        private final @NtnSignalStrength.NtnSignalStrengthLevel int mMaxNtnSignalStrengthLevel;
 
         private SatelliteSessionParams(Builder builder) {
             this.mSatelliteServiceInitializationResult =
                     builder.mSatelliteServiceInitializationResult;
             this.mSatelliteTechnology = builder.mSatelliteTechnology;
+            this.mTerminationResult = builder.mTerminationResult;
+            this.mInitializationProcessingTimeMillis = builder.mInitializationProcessingTimeMillis;
+            this.mTerminationProcessingTimeMillis =
+                    builder.mTerminationProcessingTimeMillis;
+            this.mSessionDurationSec = builder.mSessionDurationSec;
+            this.mCountOfOutgoingDatagramSuccess = builder.mCountOfOutgoingDatagramSuccess;
+            this.mCountOfOutgoingDatagramFailed = builder.mCountOfOutgoingDatagramFailed;
+            this.mCountOfIncomingDatagramSuccess = builder.mCountOfIncomingDatagramSuccess;
+            this.mCountOfIncomingDatagramFailed = builder.mCountOfIncomingDatagramFailed;
+            this.mIsDemoMode = builder.mIsDemoMode;
+            this.mMaxNtnSignalStrengthLevel = builder.mMaxNtnSignalStrengthLevel;
         }
 
         public int getSatelliteServiceInitializationResult() {
@@ -400,12 +656,63 @@
             return mSatelliteTechnology;
         }
 
+        public int getTerminationResult() {
+            return mTerminationResult;
+        }
+
+        public long getInitializationProcessingTime() {
+            return mInitializationProcessingTimeMillis;
+        }
+
+        public long getTerminationProcessingTime() {
+            return mTerminationProcessingTimeMillis;
+        }
+
+        public int getSessionDuration() {
+            return mSessionDurationSec;
+        }
+
+        public int getCountOfOutgoingDatagramSuccess() {
+            return mCountOfOutgoingDatagramSuccess;
+        }
+
+        public int getCountOfOutgoingDatagramFailed() {
+            return mCountOfOutgoingDatagramFailed;
+        }
+
+        public int getCountOfIncomingDatagramSuccess() {
+            return mCountOfIncomingDatagramSuccess;
+        }
+
+        public int getCountOfIncomingDatagramFailed() {
+            return mCountOfIncomingDatagramFailed;
+        }
+
+        public boolean getIsDemoMode() {
+            return mIsDemoMode;
+        }
+
+        public @NtnSignalStrength.NtnSignalStrengthLevel int getMaxNtnSignalStrengthLevel() {
+            return mMaxNtnSignalStrengthLevel;
+        }
+
         /**
          * A builder class to create {@link SatelliteSessionParams} data structure class
          */
         public static class Builder {
             private int mSatelliteServiceInitializationResult = -1;
             private int mSatelliteTechnology = -1;
+            private int mTerminationResult = -1;
+            private long mInitializationProcessingTimeMillis = -1;
+            private long mTerminationProcessingTimeMillis = -1;
+            private int mSessionDurationSec = -1;
+            private int mCountOfOutgoingDatagramSuccess = -1;
+            private int mCountOfOutgoingDatagramFailed = -1;
+            private int mCountOfIncomingDatagramSuccess = -1;
+            private int mCountOfIncomingDatagramFailed = -1;
+            private boolean mIsDemoMode = false;
+            private @NtnSignalStrength.NtnSignalStrengthLevel int mMaxNtnSignalStrengthLevel =
+                    NTN_SIGNAL_STRENGTH_NONE;
 
             /**
              * Sets satelliteServiceInitializationResult value of {@link SatelliteSession}
@@ -426,6 +733,68 @@
                 return this;
             }
 
+            /** Sets the satellite de-initialization result. */
+            public Builder setTerminationResult(
+                    @SatelliteManager.SatelliteResult int result) {
+                this.mTerminationResult = result;
+                return this;
+            }
+
+            /** Sets the satellite initialization processing time. */
+            public Builder setInitializationProcessingTime(long processingTime) {
+                this.mInitializationProcessingTimeMillis = processingTime;
+                return this;
+            }
+
+            /** Sets the satellite de-initialization processing time. */
+            public Builder setTerminationProcessingTime(long processingTime) {
+                this.mTerminationProcessingTimeMillis = processingTime;
+                return this;
+            }
+
+            /** Sets the total enabled time for the satellite session. */
+            public Builder setSessionDuration(int sessionDurationSec) {
+                this.mSessionDurationSec = sessionDurationSec;
+                return this;
+            }
+
+            /** Sets the total number of successful outgoing datagram transmission. */
+            public Builder setCountOfOutgoingDatagramSuccess(int countOfoutgoingDatagramSuccess) {
+                this.mCountOfOutgoingDatagramSuccess = countOfoutgoingDatagramSuccess;
+                return this;
+            }
+
+            /** Sets the total number of failed outgoing datagram transmission. */
+            public Builder setCountOfOutgoingDatagramFailed(int countOfoutgoingDatagramFailed) {
+                this.mCountOfOutgoingDatagramFailed = countOfoutgoingDatagramFailed;
+                return this;
+            }
+
+            /** Sets the total number of successful incoming datagram transmission. */
+            public Builder setCountOfIncomingDatagramSuccess(int countOfincomingDatagramSuccess) {
+                this.mCountOfIncomingDatagramSuccess = countOfincomingDatagramSuccess;
+                return this;
+            }
+
+            /** Sets the total number of failed incoming datagram transmission. */
+            public Builder setCountOfIncomingDatagramFailed(int countOfincomingDatagramFailed) {
+                this.mCountOfIncomingDatagramFailed = countOfincomingDatagramFailed;
+                return this;
+            }
+
+            /** Sets whether enabled satellite session is for demo mode or not. */
+            public Builder setIsDemoMode(boolean isDemoMode) {
+                this.mIsDemoMode = isDemoMode;
+                return this;
+            }
+
+            /** Sets the max ntn signal strength for the satellite session */
+            public Builder setMaxNtnSignalStrengthLevel(
+                    @NtnSignalStrength.NtnSignalStrengthLevel int maxNtnSignalStrengthLevel) {
+                this.mMaxNtnSignalStrengthLevel = maxNtnSignalStrengthLevel;
+                return this;
+            }
+
             /**
              * Returns SessionParams, which contains whole component of
              * {@link SatelliteSession} atom
@@ -441,7 +810,16 @@
             return "SessionParams("
                     + ", satelliteServiceInitializationResult="
                     + mSatelliteServiceInitializationResult
-                    + ", satelliteTechnology=" + mSatelliteTechnology
+                    + ", TerminationResult=" + mTerminationResult
+                    + ", InitializationProcessingTimeMillis=" + mInitializationProcessingTimeMillis
+                    + ", TerminationProcessingTimeMillis=" + mTerminationProcessingTimeMillis
+                    + ", SessionDurationSec=" + mSessionDurationSec
+                    + ", CountOfOutgoingDatagramSuccess=" + mCountOfOutgoingDatagramSuccess
+                    + ", CountOfOutgoingDatagramFailed=" + mCountOfOutgoingDatagramFailed
+                    + ", CountOfIncomingDatagramSuccess=" + mCountOfIncomingDatagramSuccess
+                    + ", CountOfIncomingDatagramFailed=" + mCountOfIncomingDatagramFailed
+                    + ", IsDemoMode=" + mIsDemoMode
+                    + ", MaxNtnSignalStrengthLevel=" + mMaxNtnSignalStrengthLevel
                     + ")";
         }
     }
@@ -454,11 +832,13 @@
         private final int mResultCode;
         private final int mDatagramSizeBytes;
         private final long mDatagramTransferTimeMillis;
+        private final boolean mIsDemoMode;
 
         private SatelliteIncomingDatagramParams(Builder builder) {
             this.mResultCode = builder.mResultCode;
             this.mDatagramSizeBytes = builder.mDatagramSizeBytes;
             this.mDatagramTransferTimeMillis = builder.mDatagramTransferTimeMillis;
+            this.mIsDemoMode = builder.mIsDemoMode;
         }
 
         public int getResultCode() {
@@ -473,6 +853,10 @@
             return mDatagramTransferTimeMillis;
         }
 
+        public boolean getIsDemoMode() {
+            return mIsDemoMode;
+        }
+
         /**
          * A builder class to create {@link SatelliteIncomingDatagramParams} data structure class
          */
@@ -480,6 +864,7 @@
             private int mResultCode = -1;
             private int mDatagramSizeBytes = -1;
             private long mDatagramTransferTimeMillis = -1;
+            private boolean mIsDemoMode = false;
 
             /**
              * Sets resultCode value of {@link SatelliteIncomingDatagram} atom
@@ -509,6 +894,15 @@
             }
 
             /**
+             * Sets whether transferred datagram is in demo mode or not
+             * then returns Builder class
+             */
+            public Builder setIsDemoMode(boolean isDemoMode) {
+                this.mIsDemoMode = isDemoMode;
+                return this;
+            }
+
+            /**
              * Returns IncomingDatagramParams, which contains whole component of
              * {@link SatelliteIncomingDatagram} atom
              */
@@ -523,7 +917,9 @@
             return "IncomingDatagramParams("
                     + ", resultCode=" + mResultCode
                     + ", datagramSizeBytes=" + mDatagramSizeBytes
-                    + ", datagramTransferTimeMillis=" + mDatagramTransferTimeMillis + ")";
+                    + ", datagramTransferTimeMillis=" + mDatagramTransferTimeMillis
+                    + ", isDemoMode=" + mIsDemoMode
+                    + ")";
         }
     }
 
@@ -536,12 +932,14 @@
         private final int mResultCode;
         private final int mDatagramSizeBytes;
         private final long mDatagramTransferTimeMillis;
+        private final boolean mIsDemoMode;
 
         private SatelliteOutgoingDatagramParams(Builder builder) {
             this.mDatagramType = builder.mDatagramType;
             this.mResultCode = builder.mResultCode;
             this.mDatagramSizeBytes = builder.mDatagramSizeBytes;
             this.mDatagramTransferTimeMillis = builder.mDatagramTransferTimeMillis;
+            this.mIsDemoMode = builder.mIsDemoMode;
         }
 
         public int getDatagramType() {
@@ -560,6 +958,10 @@
             return mDatagramTransferTimeMillis;
         }
 
+        public boolean getIsDemoMode() {
+            return mIsDemoMode;
+        }
+
         /**
          * A builder class to create {@link SatelliteOutgoingDatagramParams} data structure class
          */
@@ -568,6 +970,7 @@
             private int mResultCode = -1;
             private int mDatagramSizeBytes = -1;
             private long mDatagramTransferTimeMillis = -1;
+            private boolean mIsDemoMode = false;
 
             /**
              * Sets datagramType value of {@link SatelliteOutgoingDatagram} atom
@@ -606,6 +1009,15 @@
             }
 
             /**
+             * Sets whether transferred datagram is in demo mode or not
+             * then returns Builder class
+             */
+            public Builder setIsDemoMode(boolean isDemoMode) {
+                this.mIsDemoMode = isDemoMode;
+                return this;
+            }
+
+            /**
              * Returns OutgoingDatagramParams, which contains whole component of
              * {@link SatelliteOutgoingDatagram} atom
              */
@@ -621,7 +1033,9 @@
                     + "datagramType=" + mDatagramType
                     + ", resultCode=" + mResultCode
                     + ", datagramSizeBytes=" + mDatagramSizeBytes
-                    + ", datagramTransferTimeMillis=" + mDatagramTransferTimeMillis + ")";
+                    + ", datagramTransferTimeMillis=" + mDatagramTransferTimeMillis
+                    + ", isDemoMode=" + mIsDemoMode
+                    + ")";
         }
     }
 
@@ -877,6 +1291,854 @@
         }
     }
 
+    /**
+     * A data class to contain whole component of {@link CarrierRoamingSatelliteSession} atom.
+     * Refer to {@link #onCarrierRoamingSatelliteSessionMetrics(
+     * CarrierRoamingSatelliteSessionParams)}.
+     */
+    public class CarrierRoamingSatelliteSessionParams {
+        private final int mCarrierId;
+        private final boolean mIsNtnRoamingInHomeCountry;
+        private final int mTotalSatelliteModeTimeSec;
+        private final int mNumberOfSatelliteConnections;
+        private final int mAvgDurationOfSatelliteConnectionSec;
+        private final int mSatelliteConnectionGapMinSec;
+        private final int mSatelliteConnectionGapAvgSec;
+        private final int mSatelliteConnectionGapMaxSec;
+        private final int mRsrpAvg;
+        private final int mRsrpMedian;
+        private final int mRssnrAvg;
+        private final int mRssnrMedian;
+        private final int mCountOfIncomingSms;
+        private final int mCountOfOutgoingSms;
+        private final int mCountOfIncomingMms;
+        private final int mCountOfOutgoingMms;
+
+        private CarrierRoamingSatelliteSessionParams(Builder builder) {
+            this.mCarrierId = builder.mCarrierId;
+            this.mIsNtnRoamingInHomeCountry = builder.mIsNtnRoamingInHomeCountry;
+            this.mTotalSatelliteModeTimeSec = builder.mTotalSatelliteModeTimeSec;
+            this.mNumberOfSatelliteConnections = builder.mNumberOfSatelliteConnections;
+            this.mAvgDurationOfSatelliteConnectionSec =
+                    builder.mAvgDurationOfSatelliteConnectionSec;
+            this.mSatelliteConnectionGapMinSec = builder.mSatelliteConnectionGapMinSec;
+            this.mSatelliteConnectionGapAvgSec = builder.mSatelliteConnectionGapAvgSec;
+            this.mSatelliteConnectionGapMaxSec = builder.mSatelliteConnectionGapMaxSec;
+            this.mRsrpAvg = builder.mRsrpAvg;
+            this.mRsrpMedian = builder.mRsrpMedian;
+            this.mRssnrAvg = builder.mRssnrAvg;
+            this.mRssnrMedian = builder.mRssnrMedian;
+            this.mCountOfIncomingSms = builder.mCountOfIncomingSms;
+            this.mCountOfOutgoingSms = builder.mCountOfOutgoingSms;
+            this.mCountOfIncomingMms = builder.mCountOfIncomingMms;
+            this.mCountOfOutgoingMms = builder.mCountOfOutgoingMms;
+        }
+
+        public int getCarrierId() {
+            return mCarrierId;
+        }
+
+        public boolean getIsNtnRoamingInHomeCountry() {
+            return mIsNtnRoamingInHomeCountry;
+        }
+
+        public int getTotalSatelliteModeTimeSec() {
+            return mTotalSatelliteModeTimeSec;
+        }
+
+        public int getNumberOfSatelliteConnections() {
+            return mNumberOfSatelliteConnections;
+        }
+
+        public int getAvgDurationOfSatelliteConnectionSec() {
+            return mAvgDurationOfSatelliteConnectionSec;
+        }
+
+        public int getSatelliteConnectionGapMinSec() {
+            return mSatelliteConnectionGapMinSec;
+        }
+
+        public int getSatelliteConnectionGapAvgSec() {
+            return mSatelliteConnectionGapAvgSec;
+        }
+
+        public int getSatelliteConnectionGapMaxSec() {
+            return mSatelliteConnectionGapMaxSec;
+        }
+
+        public int getRsrpAvg() {
+            return mRsrpAvg;
+        }
+
+        public int getRsrpMedian() {
+            return mRsrpMedian;
+        }
+
+        public int getRssnrAvg() {
+            return mRssnrAvg;
+        }
+
+        public int getRssnrMedian() {
+            return mRssnrMedian;
+        }
+
+        public int getCountOfIncomingSms() {
+            return mCountOfIncomingSms;
+        }
+
+        public int getCountOfOutgoingSms() {
+            return mCountOfOutgoingSms;
+        }
+
+        public int getCountOfIncomingMms() {
+            return mCountOfIncomingMms;
+        }
+
+        public int getCountOfOutgoingMms() {
+            return mCountOfOutgoingMms;
+        }
+
+        /**
+         * A builder class to create {@link CarrierRoamingSatelliteSessionParams} data structure
+         * class
+         */
+        public static class Builder {
+            private int mCarrierId = -1;
+            private boolean mIsNtnRoamingInHomeCountry = false;
+            private int mTotalSatelliteModeTimeSec = 0;
+            private int mNumberOfSatelliteConnections = 0;
+            private int mAvgDurationOfSatelliteConnectionSec = 0;
+            private int mSatelliteConnectionGapMinSec = 0;
+            private int mSatelliteConnectionGapAvgSec = 0;
+            private int mSatelliteConnectionGapMaxSec = 0;
+            private int mRsrpAvg = 0;
+            private int mRsrpMedian = 0;
+            private int mRssnrAvg = 0;
+            private int mRssnrMedian = 0;
+            private int mCountOfIncomingSms = 0;
+            private int mCountOfOutgoingSms = 0;
+            private int mCountOfIncomingMms = 0;
+            private int mCountOfOutgoingMms = 0;
+
+            /**
+             * Sets carrierId value of {@link CarrierRoamingSatelliteSession} atom
+             * then returns Builder class
+             */
+            public Builder setCarrierId(int carrierId) {
+                this.mCarrierId = carrierId;
+                return this;
+            }
+
+            /**
+             * Sets isNtnRoamingInHomeCountry value of {@link CarrierRoamingSatelliteSession} atom
+             * then returns Builder class
+             */
+            public Builder setIsNtnRoamingInHomeCountry(boolean isNtnRoamingInHomeCountry) {
+                this.mIsNtnRoamingInHomeCountry = isNtnRoamingInHomeCountry;
+                return this;
+            }
+
+            /**
+             * Sets totalSatelliteModeTimeSec value of {@link CarrierRoamingSatelliteSession} atom
+             * then returns Builder class
+             */
+            public Builder setTotalSatelliteModeTimeSec(int totalSatelliteModeTimeSec) {
+                this.mTotalSatelliteModeTimeSec = totalSatelliteModeTimeSec;
+                return this;
+            }
+
+
+            /**
+             * Sets numberOfSatelliteConnections value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setNumberOfSatelliteConnections(int numberOfSatelliteConnections) {
+                this.mNumberOfSatelliteConnections = numberOfSatelliteConnections;
+                return this;
+            }
+
+            /**
+             * Sets avgDurationOfSatelliteConnectionSec value of
+             * {@link CarrierRoamingSatelliteSession} atom then returns Builder class
+             */
+            public Builder setAvgDurationOfSatelliteConnectionSec(
+                    int avgDurationOfSatelliteConnectionSec) {
+                this.mAvgDurationOfSatelliteConnectionSec = avgDurationOfSatelliteConnectionSec;
+                return this;
+            }
+
+            /**
+             * Sets satelliteConnectionGapMinSec value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setSatelliteConnectionGapMinSec(int satelliteConnectionGapMinSec) {
+                this.mSatelliteConnectionGapMinSec = satelliteConnectionGapMinSec;
+                return this;
+            }
+
+            /**
+             * Sets satelliteConnectionGapAvgSec value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setSatelliteConnectionGapAvgSec(int satelliteConnectionGapAvgSec) {
+                this.mSatelliteConnectionGapAvgSec = satelliteConnectionGapAvgSec;
+                return this;
+            }
+
+            /**
+             * Sets satelliteConnectionGapMaxSec value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setSatelliteConnectionGapMaxSec(int satelliteConnectionGapMaxSec) {
+                this.mSatelliteConnectionGapMaxSec = satelliteConnectionGapMaxSec;
+                return this;
+            }
+
+            /**
+             * Sets rsrpAvg value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setRsrpAvg(int rsrpAvg) {
+                this.mRsrpAvg = rsrpAvg;
+                return this;
+            }
+
+            /**
+             * Sets rsrpMedian value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setRsrpMedian(int rsrpMedian) {
+                this.mRsrpMedian = rsrpMedian;
+                return this;
+            }
+
+            /**
+             * Sets rssnrAvg value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setRssnrAvg(int rssnrAvg) {
+                this.mRssnrAvg = rssnrAvg;
+                return this;
+            }
+
+            /**
+             * Sets rssnrMedian value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setRssnrMedian(int rssnrMedian) {
+                this.mRssnrMedian = rssnrMedian;
+                return this;
+            }
+
+
+            /**
+             * Sets countOfIncomingSms value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfIncomingSms(int countOfIncomingSms) {
+                this.mCountOfIncomingSms = countOfIncomingSms;
+                return this;
+            }
+
+            /**
+             * Sets countOfOutgoingSms value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfOutgoingSms(int countOfOutgoingSms) {
+                this.mCountOfOutgoingSms = countOfOutgoingSms;
+                return this;
+            }
+
+            /**
+             * Sets countOfIncomingMms value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfIncomingMms(int countOfIncomingMms) {
+                this.mCountOfIncomingMms = countOfIncomingMms;
+                return this;
+            }
+
+            /**
+             * Sets countOfOutgoingMms value of {@link CarrierRoamingSatelliteSession}
+             * atom then returns Builder class
+             */
+            public Builder setCountOfOutgoingMms(int countOfOutgoingMms) {
+                this.mCountOfOutgoingMms = countOfOutgoingMms;
+                return this;
+            }
+
+            /**
+             * Returns CarrierRoamingSatelliteSessionParams, which contains whole component of
+             * {@link CarrierRoamingSatelliteSession} atom
+             */
+            public CarrierRoamingSatelliteSessionParams build() {
+                return new SatelliteStats()
+                        .new CarrierRoamingSatelliteSessionParams(Builder.this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "CarrierRoamingSatelliteSessionParams("
+                    + "carrierId=" + mCarrierId
+                    + ", isNtnRoamingInHomeCountry=" + mIsNtnRoamingInHomeCountry
+                    + ", totalSatelliteModeTimeSec=" + mTotalSatelliteModeTimeSec
+                    + ", numberOfSatelliteConnections=" + mNumberOfSatelliteConnections
+                    + ", avgDurationOfSatelliteConnectionSec="
+                    + mAvgDurationOfSatelliteConnectionSec
+                    + ", satelliteConnectionGapMinSec=" + mSatelliteConnectionGapMinSec
+                    + ", satelliteConnectionGapAvgSec=" + mSatelliteConnectionGapAvgSec
+                    + ", satelliteConnectionGapMaxSec=" + mSatelliteConnectionGapMaxSec
+                    + ", rsrpAvg=" + mRsrpAvg
+                    + ", rsrpMedian=" + mRsrpMedian
+                    + ", rssnrAvg=" + mRssnrAvg
+                    + ", rssnrMedian=" + mRssnrMedian
+                    + ", countOfIncomingSms=" + mCountOfIncomingSms
+                    + ", countOfOutgoingSms=" + mCountOfOutgoingSms
+                    + ", countOfIncomingMms=" + mCountOfIncomingMms
+                    + ", countOfOutgoingMms=" + mCountOfOutgoingMms
+                    + ")";
+        }
+    }
+
+    /**
+     * A data class to contain whole component of {@link CarrierRoamingSatelliteControllerStats}
+     * atom. Refer to {@link #onCarrierRoamingSatelliteControllerStatsMetrics(
+     * CarrierRoamingSatelliteControllerStatsParams)}.
+     */
+    public class CarrierRoamingSatelliteControllerStatsParams {
+        private final int mConfigDataSource;
+        private final int mCountOfEntitlementStatusQueryRequest;
+        private final int mCountOfSatelliteConfigUpdateRequest;
+        private final int mCountOfSatelliteNotificationDisplayed;
+        private final int mSatelliteSessionGapMinSec;
+        private final int mSatelliteSessionGapAvgSec;
+        private final int mSatelliteSessionGapMaxSec;
+
+        private CarrierRoamingSatelliteControllerStatsParams(Builder builder) {
+            this.mConfigDataSource = builder.mConfigDataSource;
+            this.mCountOfEntitlementStatusQueryRequest =
+                    builder.mCountOfEntitlementStatusQueryRequest;
+            this.mCountOfSatelliteConfigUpdateRequest =
+                    builder.mCountOfSatelliteConfigUpdateRequest;
+            this.mCountOfSatelliteNotificationDisplayed =
+                    builder.mCountOfSatelliteNotificationDisplayed;
+            this.mSatelliteSessionGapMinSec = builder.mSatelliteSessionGapMinSec;
+            this.mSatelliteSessionGapAvgSec = builder.mSatelliteSessionGapAvgSec;
+            this.mSatelliteSessionGapMaxSec = builder.mSatelliteSessionGapMaxSec;
+        }
+
+        public int getConfigDataSource() {
+            return mConfigDataSource;
+        }
+
+
+        public int getCountOfEntitlementStatusQueryRequest() {
+            return mCountOfEntitlementStatusQueryRequest;
+        }
+
+        public int getCountOfSatelliteConfigUpdateRequest() {
+            return mCountOfSatelliteConfigUpdateRequest;
+        }
+
+        public int getCountOfSatelliteNotificationDisplayed() {
+            return mCountOfSatelliteNotificationDisplayed;
+        }
+
+        public int getSatelliteSessionGapMinSec() {
+            return mSatelliteSessionGapMinSec;
+        }
+
+        public int getSatelliteSessionGapAvgSec() {
+            return mSatelliteSessionGapAvgSec;
+        }
+
+        public int getSatelliteSessionGapMaxSec() {
+            return mSatelliteSessionGapMaxSec;
+        }
+
+        /**
+         * A builder class to create {@link CarrierRoamingSatelliteControllerStatsParams}
+         * data structure class
+         */
+        public static class Builder {
+            private int mConfigDataSource = SatelliteConstants.CONFIG_DATA_SOURCE_UNKNOWN;
+            private int mCountOfEntitlementStatusQueryRequest = 0;
+            private int mCountOfSatelliteConfigUpdateRequest = 0;
+            private int mCountOfSatelliteNotificationDisplayed = 0;
+            private int mSatelliteSessionGapMinSec = 0;
+            private int mSatelliteSessionGapAvgSec = 0;
+            private int mSatelliteSessionGapMaxSec = 0;
+
+            /**
+             * Sets configDataSource value of {@link CarrierRoamingSatelliteControllerStats} atom
+             * then returns Builder class
+             */
+            public Builder setConfigDataSource(int configDataSource) {
+                this.mConfigDataSource = configDataSource;
+                return this;
+            }
+
+            /**
+             * Sets countOfEntitlementStatusQueryRequest value of
+             * {@link CarrierRoamingSatelliteControllerStats} atom then returns Builder class
+             */
+            public Builder setCountOfEntitlementStatusQueryRequest(
+                    int countOfEntitlementStatusQueryRequest) {
+                this.mCountOfEntitlementStatusQueryRequest = countOfEntitlementStatusQueryRequest;
+                return this;
+            }
+
+            /**
+             * Sets countOfSatelliteConfigUpdateRequest value of
+             * {@link CarrierRoamingSatelliteControllerStats} atom then returns Builder class
+             */
+            public Builder setCountOfSatelliteConfigUpdateRequest(
+                    int countOfSatelliteConfigUpdateRequest) {
+                this.mCountOfSatelliteConfigUpdateRequest = countOfSatelliteConfigUpdateRequest;
+                return this;
+            }
+
+            /**
+             * Sets countOfSatelliteNotificationDisplayed value of
+             * {@link CarrierRoamingSatelliteControllerStats} atom then returns Builder class
+             */
+            public Builder setCountOfSatelliteNotificationDisplayed(
+                    int countOfSatelliteNotificationDisplayed) {
+                this.mCountOfSatelliteNotificationDisplayed = countOfSatelliteNotificationDisplayed;
+                return this;
+            }
+
+            /**
+             * Sets satelliteSessionGapMinSec value of
+             * {@link CarrierRoamingSatelliteControllerStats} atom then returns Builder class
+             */
+            public Builder setSatelliteSessionGapMinSec(int satelliteSessionGapMinSec) {
+                this.mSatelliteSessionGapMinSec = satelliteSessionGapMinSec;
+                return this;
+            }
+
+            /**
+             * Sets satelliteSessionGapAvgSec value of
+             * {@link CarrierRoamingSatelliteControllerStats} atom then returns Builder class
+             */
+            public Builder setSatelliteSessionGapAvgSec(int satelliteSessionGapAvgSec) {
+                this.mSatelliteSessionGapAvgSec = satelliteSessionGapAvgSec;
+                return this;
+            }
+
+            /**
+             * Sets satelliteSessionGapMaxSec value of
+             * {@link CarrierRoamingSatelliteControllerStats} atom then returns Builder class
+             */
+            public Builder setSatelliteSessionGapMaxSec(int satelliteSessionGapMaxSec) {
+                this.mSatelliteSessionGapMaxSec = satelliteSessionGapMaxSec;
+                return this;
+            }
+
+            /**
+             * Returns CarrierRoamingSatelliteControllerStatsParams, which contains whole component
+             * of {@link CarrierRoamingSatelliteControllerStats} atom
+             */
+            public CarrierRoamingSatelliteControllerStatsParams build() {
+                return new SatelliteStats()
+                        .new CarrierRoamingSatelliteControllerStatsParams(Builder.this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "CarrierRoamingSatelliteControllerStatsParams("
+                    + "configDataSource=" + mConfigDataSource
+                    + ", countOfEntitlementStatusQueryRequest="
+                    + mCountOfEntitlementStatusQueryRequest
+                    + ", countOfSatelliteConfigUpdateRequest="
+                    + mCountOfSatelliteConfigUpdateRequest
+                    + ", countOfSatelliteNotificationDisplayed="
+                    + mCountOfSatelliteNotificationDisplayed
+                    + ", satelliteSessionGapMinSec=" + mSatelliteSessionGapMinSec
+                    + ", satelliteSessionGapAvgSec=" + mSatelliteSessionGapAvgSec
+                    + ", satelliteSessionGapMaxSec=" + mSatelliteSessionGapMaxSec
+                    + ")";
+        }
+    }
+
+    /**
+     * A data class to contain whole component of {@link SatelliteEntitlement} atom.
+     * Refer to {@link #onSatelliteEntitlementMetrics(SatelliteEntitlementParams)}.
+     */
+    public class SatelliteEntitlementParams {
+        private final int mCarrierId;
+        private final int mResult;
+        private final int mEntitlementStatus;
+        private final boolean mIsRetry;
+        private final int mCount;
+
+        private SatelliteEntitlementParams(Builder builder) {
+            this.mCarrierId = builder.mCarrierId;
+            this.mResult = builder.mResult;
+            this.mEntitlementStatus = builder.mEntitlementStatus;
+            this.mIsRetry = builder.mIsRetry;
+            this.mCount = builder.mCount;
+        }
+
+        public int getCarrierId() {
+            return mCarrierId;
+        }
+
+        public int getResult() {
+            return mResult;
+        }
+
+        public int getEntitlementStatus() {
+            return mEntitlementStatus;
+        }
+
+        public boolean getIsRetry() {
+            return mIsRetry;
+        }
+
+        public int getCount() {
+            return mCount;
+        }
+
+        /**
+         * A builder class to create {@link SatelliteEntitlementParams} data structure class
+         */
+        public static class Builder {
+            private int mCarrierId = -1;
+            private int mResult = -1;
+            private int mEntitlementStatus = -1;
+            private boolean mIsRetry = false;
+            private int mCount = -1;
+
+            /**
+             * Sets carrierId value of {@link SatelliteEntitlement} atom
+             * then returns Builder class
+             */
+            public Builder setCarrierId(int carrierId) {
+                this.mCarrierId = carrierId;
+                return this;
+            }
+
+            /**
+             * Sets result value of {@link SatelliteEntitlement} atom
+             * then returns Builder class
+             */
+            public Builder setResult(int result) {
+                this.mResult = result;
+                return this;
+            }
+
+            /**
+             * Sets entitlementStatus value of {@link SatelliteEntitlement} atom
+             * then returns Builder class
+             */
+            public Builder setEntitlementStatus(int entitlementStatus) {
+                this.mEntitlementStatus = entitlementStatus;
+                return this;
+            }
+
+            /**
+             * Sets isRetry value of {@link SatelliteEntitlement} atom
+             * then returns Builder class
+             */
+            public Builder setIsRetry(boolean isRetry) {
+                this.mIsRetry = isRetry;
+                return this;
+            }
+
+            /**
+             * Sets count value of {@link SatelliteEntitlement} atom
+             * then returns Builder class
+             */
+            public Builder setCount(int count) {
+                this.mCount = count;
+                return this;
+            }
+
+            /**
+             * Returns SatelliteEntitlementParams, which contains whole component of
+             * {@link SatelliteEntitlement} atom
+             */
+            public SatelliteEntitlementParams build() {
+                return new SatelliteStats()
+                        .new SatelliteEntitlementParams(Builder.this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "SatelliteEntitlementParams("
+                    + "carrierId=" + mCarrierId
+                    + ", result=" + mResult
+                    + ", entitlementStatus=" + mEntitlementStatus
+                    + ", isRetry=" + mIsRetry
+                    + ", count=" + mCount + ")";
+        }
+    }
+
+    /**
+     * A data class to contain whole component of {@link SatelliteConfigUpdater} atom.
+     * Refer to {@link #onSatelliteConfigUpdaterMetrics(SatelliteConfigUpdaterParams)}.
+     */
+    public class SatelliteConfigUpdaterParams {
+        private final int mConfigVersion;
+        private final int mOemConfigResult;
+        private final int mCarrierConfigResult;
+        private final int mCount;
+
+        private SatelliteConfigUpdaterParams(Builder builder) {
+            this.mConfigVersion = builder.mConfigVersion;
+            this.mOemConfigResult = builder.mOemConfigResult;
+            this.mCarrierConfigResult = builder.mCarrierConfigResult;
+            this.mCount = builder.mCount;
+        }
+
+        public int getConfigVersion() {
+            return mConfigVersion;
+        }
+
+        public int getOemConfigResult() {
+            return mOemConfigResult;
+        }
+
+        public int getCarrierConfigResult() {
+            return mCarrierConfigResult;
+        }
+
+        public int getCount() {
+            return mCount;
+        }
+
+        /**
+         * A builder class to create {@link SatelliteConfigUpdaterParams} data structure class
+         */
+        public static class Builder {
+            private int mConfigVersion = -1;
+            private int mOemConfigResult = -1;
+            private int mCarrierConfigResult = -1;
+            private int mCount = -1;
+
+            /**
+             * Sets configVersion value of {@link SatelliteConfigUpdater} atom
+             * then returns Builder class
+             */
+            public Builder setConfigVersion(int configVersion) {
+                this.mConfigVersion = configVersion;
+                return this;
+            }
+
+            /**
+             * Sets oemConfigResult value of {@link SatelliteConfigUpdater} atom
+             * then returns Builder class
+             */
+            public Builder setOemConfigResult(int oemConfigResult) {
+                this.mOemConfigResult = oemConfigResult;
+                return this;
+            }
+
+            /**
+             * Sets carrierConfigResult value of {@link SatelliteConfigUpdater} atom
+             * then returns Builder class
+             */
+            public Builder setCarrierConfigResult(int carrierConfigResult) {
+                this.mCarrierConfigResult = carrierConfigResult;
+                return this;
+            }
+
+            /**
+             * Sets count value of {@link SatelliteConfigUpdater} atom
+             * then returns Builder class
+             */
+            public Builder setCount(int count) {
+                this.mCount = count;
+                return this;
+            }
+
+            /**
+             * Returns SatelliteConfigUpdaterParams, which contains whole component of
+             * {@link SatelliteConfigUpdater} atom
+             */
+            public SatelliteConfigUpdaterParams build() {
+                return new SatelliteStats()
+                        .new SatelliteConfigUpdaterParams(Builder.this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "SatelliteConfigUpdaterParams("
+                    + "configVersion=" + mConfigVersion
+                    + ", oemConfigResult=" + mOemConfigResult
+                    + ", carrierConfigResult=" + mCarrierConfigResult
+                    + ", count=" + mCount + ")";
+        }
+    }
+
+    /**
+     * A data class to contain whole component of {@link SatelliteAccessControllerParams} atom.
+     * Refer to {@link #onSatelliteAccessControllerMetrics(SatelliteAccessControllerParams)}.
+     */
+    public class SatelliteAccessControllerParams {
+        private final @SatelliteConstants.AccessControlType int mAccessControlType;
+        private final long mLocationQueryTimeMillis;
+        private final long mOnDeviceLookupTimeMillis;
+        private final long mTotalCheckingTimeMillis;
+        private final boolean mIsAllowed;
+        private final boolean mIsEmergency;
+        private final @SatelliteManager.SatelliteResult int mResultCode;
+        private final String[] mCountryCodes;
+        private final @SatelliteConstants.ConfigDataSource int mConfigDataSource;
+
+        private SatelliteAccessControllerParams(Builder builder) {
+            this.mAccessControlType = builder.mAccessControlType;
+            this.mLocationQueryTimeMillis = builder.mLocationQueryTimeMillis;
+            this.mOnDeviceLookupTimeMillis = builder.mOnDeviceLookupTimeMillis;
+            this.mTotalCheckingTimeMillis = builder.mTotalCheckingTimeMillis;
+            this.mIsAllowed = builder.mIsAllowed;
+            this.mIsEmergency = builder.mIsEmergency;
+            this.mResultCode = builder.mResultCode;
+            this.mCountryCodes = builder.mCountryCodes;
+            this.mConfigDataSource = builder.mConfigDataSource;
+        }
+
+        public @SatelliteConstants.AccessControlType int getAccessControlType() {
+            return mAccessControlType;
+        }
+
+        public long getLocationQueryTime() {
+            return mLocationQueryTimeMillis;
+        }
+
+        public long getOnDeviceLookupTime() {
+            return mOnDeviceLookupTimeMillis;
+        }
+
+        public long getTotalCheckingTime() {
+            return mTotalCheckingTimeMillis;
+        }
+
+        public boolean getIsAllowed() {
+            return mIsAllowed;
+        }
+
+        public boolean getIsEmergency() {
+            return mIsEmergency;
+        }
+
+        public @SatelliteManager.SatelliteResult int getResultCode() {
+            return mResultCode;
+        }
+
+        public String[] getCountryCodes() {
+            return mCountryCodes;
+        }
+
+        public @SatelliteConstants.ConfigDataSource int getConfigDataSource() {
+            return mConfigDataSource;
+        }
+
+        /**
+         * A builder class to create {@link SatelliteAccessControllerParams} data structure class
+         */
+        public static class Builder {
+            private @SatelliteConstants.AccessControlType int mAccessControlType;
+            private long mLocationQueryTimeMillis;
+            private long mOnDeviceLookupTimeMillis;
+            private long mTotalCheckingTimeMillis;
+            private boolean mIsAllowed;
+            private boolean mIsEmergency;
+            private @SatelliteManager.SatelliteResult int mResultCode;
+            private String[] mCountryCodes;
+            private @SatelliteConstants.ConfigDataSource int mConfigDataSource;
+
+            /**
+             * Sets AccessControlType value of {@link #SatelliteAccessController}
+             * atom then returns Builder class
+             */
+            public Builder setAccessControlType(
+                    @SatelliteConstants.AccessControlType int accessControlType) {
+                this.mAccessControlType = accessControlType;
+                return this;
+            }
+
+            /** Sets the location query time for current satellite enablement. */
+            public Builder setLocationQueryTime(long locationQueryTimeMillis) {
+                this.mLocationQueryTimeMillis = locationQueryTimeMillis;
+                return this;
+            }
+
+            /** Sets the on device lookup time for current satellite enablement. */
+            public Builder setOnDeviceLookupTime(long onDeviceLookupTimeMillis) {
+                this.mOnDeviceLookupTimeMillis = onDeviceLookupTimeMillis;
+                return this;
+            }
+
+            /** Sets the total checking time for current satellite enablement. */
+            public Builder setTotalCheckingTime(long totalCheckingTimeMillis) {
+                this.mTotalCheckingTimeMillis = totalCheckingTimeMillis;
+                return this;
+            }
+
+            /** Sets whether the satellite communication is allowed from current location. */
+            public Builder setIsAllowed(boolean isAllowed) {
+                this.mIsAllowed = isAllowed;
+                return this;
+            }
+
+            /** Sets whether the current satellite enablement is for emergency or not. */
+            public Builder setIsEmergency(boolean isEmergency) {
+                this.mIsEmergency = isEmergency;
+                return this;
+            }
+
+            /** Sets the result code for checking whether satellite service is allowed from current
+             location. */
+            public Builder setResult(int result) {
+                this.mResultCode = result;
+                return this;
+            }
+
+            /** Sets the country code for current location while attempting satellite enablement. */
+            public Builder setCountryCodes(String[] countryCodes) {
+                this.mCountryCodes = Arrays.stream(countryCodes).toArray(String[]::new);
+                return this;
+            }
+
+            /** Sets the config data source for checking whether satellite service is allowed from
+             current location. */
+            public Builder setConfigDatasource(int configDatasource) {
+                this.mConfigDataSource = configDatasource;
+                return this;
+            }
+
+            /**
+             * Returns AccessControllerParams, which contains whole component of
+             * {@link #SatelliteAccessController} atom
+             */
+            public SatelliteAccessControllerParams build() {
+                return new SatelliteStats()
+                        .new SatelliteAccessControllerParams(this);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "AccessControllerParams("
+                    + ", AccessControlType=" + mAccessControlType
+                    + ", LocationQueryTime=" + mLocationQueryTimeMillis
+                    + ", OnDeviceLookupTime=" + mOnDeviceLookupTimeMillis
+                    + ", TotalCheckingTime=" + mTotalCheckingTimeMillis
+                    + ", IsAllowed=" + mIsAllowed
+                    + ", IsEmergency=" + mIsEmergency
+                    + ", ResultCode=" + mResultCode
+                    + ", CountryCodes=" + Arrays.toString(mCountryCodes)
+                    + ", ConfigDataSource=" + mConfigDataSource
+                    + ")";
+        }
+    }
+
     /**  Create a new atom or update an existing atom for SatelliteController metrics */
     public synchronized void onSatelliteControllerMetrics(SatelliteControllerParams param) {
         SatelliteController proto = new SatelliteController();
@@ -901,7 +2163,18 @@
         proto.totalServiceUptimeSec = param.getTotalServiceUptimeSec();
         proto.totalBatteryConsumptionPercent = param.getTotalBatteryConsumptionPercent();
         proto.totalBatteryChargedTimeSec = param.getTotalBatteryChargedTimeSec();
-
+        proto.countOfDemoModeSatelliteServiceEnablementsSuccess =
+                param.getCountOfDemoModeSatelliteServiceEnablementsSuccess();
+        proto.countOfDemoModeSatelliteServiceEnablementsFail =
+                param.getCountOfDemoModeSatelliteServiceEnablementsFail();
+        proto.countOfDemoModeOutgoingDatagramSuccess =
+                param.getCountOfDemoModeOutgoingDatagramSuccess();
+        proto.countOfDemoModeOutgoingDatagramFail = param.getCountOfDemoModeOutgoingDatagramFail();
+        proto.countOfDemoModeIncomingDatagramSuccess =
+                param.getCountOfDemoModeIncomingDatagramSuccess();
+        proto.countOfDemoModeIncomingDatagramFail = param.getCountOfDemoModeIncomingDatagramFail();
+        proto.countOfDatagramTypeKeepAliveSuccess = param.getCountOfDatagramTypeKeepAliveSuccess();
+        proto.countOfDatagramTypeKeepAliveFail = param.getCountOfDatagramTypeKeepAliveFail();
         mAtomsStorage.addSatelliteControllerStats(proto);
     }
 
@@ -912,6 +2185,16 @@
                 param.getSatelliteServiceInitializationResult();
         proto.satelliteTechnology = param.getSatelliteTechnology();
         proto.count = 1;
+        proto.satelliteServiceTerminationResult = param.getTerminationResult();
+        proto.initializationProcessingTimeMillis = param.getInitializationProcessingTime();
+        proto.terminationProcessingTimeMillis = param.getTerminationProcessingTime();
+        proto.sessionDurationSeconds = param.getSessionDuration();
+        proto.countOfOutgoingDatagramSuccess = param.getCountOfIncomingDatagramSuccess();
+        proto.countOfOutgoingDatagramFailed = param.getCountOfOutgoingDatagramFailed();
+        proto.countOfIncomingDatagramSuccess = param.getCountOfIncomingDatagramSuccess();
+        proto.countOfIncomingDatagramFailed = param.getCountOfOutgoingDatagramFailed();
+        proto.isDemoMode = param.getIsDemoMode();
+        proto.maxNtnSignalStrengthLevel = param.getMaxNtnSignalStrengthLevel();
         mAtomsStorage.addSatelliteSessionStats(proto);
     }
 
@@ -922,6 +2205,7 @@
         proto.resultCode = param.getResultCode();
         proto.datagramSizeBytes = param.getDatagramSizeBytes();
         proto.datagramTransferTimeMillis = param.getDatagramTransferTimeMillis();
+        proto.isDemoMode = param.getIsDemoMode();
         mAtomsStorage.addSatelliteIncomingDatagramStats(proto);
     }
 
@@ -933,6 +2217,7 @@
         proto.resultCode = param.getResultCode();
         proto.datagramSizeBytes = param.getDatagramSizeBytes();
         proto.datagramTransferTimeMillis = param.getDatagramTransferTimeMillis();
+        proto.isDemoMode = param.getIsDemoMode();
         mAtomsStorage.addSatelliteOutgoingDatagramStats(proto);
     }
 
@@ -960,4 +2245,78 @@
         proto.count = 1;
         mAtomsStorage.addSatelliteSosMessageRecommenderStats(proto);
     }
+
+    /**  Create a new atom for CarrierRoamingSatelliteSession metrics */
+    public synchronized  void onCarrierRoamingSatelliteSessionMetrics(
+            CarrierRoamingSatelliteSessionParams param) {
+        CarrierRoamingSatelliteSession proto = new CarrierRoamingSatelliteSession();
+        proto.carrierId = param.getCarrierId();
+        proto.isNtnRoamingInHomeCountry = param.getIsNtnRoamingInHomeCountry();
+        proto.totalSatelliteModeTimeSec = param.getTotalSatelliteModeTimeSec();
+        proto.numberOfSatelliteConnections = param.getNumberOfSatelliteConnections();
+        proto.avgDurationOfSatelliteConnectionSec = param.getAvgDurationOfSatelliteConnectionSec();
+        proto.satelliteConnectionGapMinSec = param.mSatelliteConnectionGapMinSec;
+        proto.satelliteConnectionGapAvgSec = param.mSatelliteConnectionGapAvgSec;
+        proto.satelliteConnectionGapMaxSec = param.mSatelliteConnectionGapMaxSec;
+        proto.rsrpAvg = param.mRsrpAvg;
+        proto.rsrpMedian = param.mRsrpMedian;
+        proto.rssnrAvg = param.mRssnrAvg;
+        proto.rssnrMedian = param.mRssnrMedian;
+        proto.countOfIncomingSms = param.mCountOfIncomingSms;
+        proto.countOfOutgoingSms = param.mCountOfOutgoingSms;
+        proto.countOfIncomingMms = param.mCountOfIncomingMms;
+        proto.countOfOutgoingMms = param.mCountOfOutgoingMms;
+        mAtomsStorage.addCarrierRoamingSatelliteSessionStats(proto);
+    }
+
+    /**  Create a new atom for CarrierRoamingSatelliteSession metrics */
+    public synchronized  void onCarrierRoamingSatelliteControllerStatsMetrics(
+            CarrierRoamingSatelliteControllerStatsParams param) {
+        CarrierRoamingSatelliteControllerStats proto = new CarrierRoamingSatelliteControllerStats();
+        proto.configDataSource = param.mConfigDataSource;
+        proto.countOfEntitlementStatusQueryRequest = param.mCountOfEntitlementStatusQueryRequest;
+        proto.countOfSatelliteConfigUpdateRequest = param.mCountOfSatelliteConfigUpdateRequest;
+        proto.countOfSatelliteNotificationDisplayed = param.mCountOfSatelliteNotificationDisplayed;
+        proto.satelliteSessionGapMinSec = param.mSatelliteSessionGapMinSec;
+        proto.satelliteSessionGapAvgSec = param.mSatelliteSessionGapAvgSec;
+        proto.satelliteSessionGapMaxSec = param.mSatelliteSessionGapMaxSec;
+        mAtomsStorage.addCarrierRoamingSatelliteControllerStats(proto);
+    }
+
+    /**  Create a new atom for SatelliteEntitlement metrics */
+    public synchronized  void onSatelliteEntitlementMetrics(SatelliteEntitlementParams param) {
+        SatelliteEntitlement proto = new SatelliteEntitlement();
+        proto.carrierId = param.getCarrierId();
+        proto.result = param.getResult();
+        proto.entitlementStatus = param.getEntitlementStatus();
+        proto.isRetry = param.getIsRetry();
+        proto.count = param.getCount();
+        mAtomsStorage.addSatelliteEntitlementStats(proto);
+    }
+
+    /**  Create a new atom for SatelliteConfigUpdater metrics */
+    public synchronized  void onSatelliteConfigUpdaterMetrics(SatelliteConfigUpdaterParams param) {
+        SatelliteConfigUpdater proto = new SatelliteConfigUpdater();
+        proto.configVersion = param.getConfigVersion();
+        proto.oemConfigResult = param.getOemConfigResult();
+        proto.carrierConfigResult = param.getCarrierConfigResult();
+        proto.count = param.getCount();
+        mAtomsStorage.addSatelliteConfigUpdaterStats(proto);
+    }
+
+    /**  Create a new atom or update an existing atom for SatelliteAccessController metrics */
+    public synchronized void onSatelliteAccessControllerMetrics(
+            SatelliteAccessControllerParams param) {
+        SatelliteAccessController proto = new SatelliteAccessController();
+        proto.accessControlType = param.getAccessControlType();
+        proto.locationQueryTimeMillis = param.getLocationQueryTime();
+        proto.onDeviceLookupTimeMillis = param.getOnDeviceLookupTime();
+        proto.totalCheckingTimeMillis = param.getTotalCheckingTime();
+        proto.isAllowed = param.getIsAllowed();
+        proto.isEmergency = param.getIsEmergency();
+        proto.resultCode = param.getResultCode();
+        proto.countryCodes = param.getCountryCodes();
+        proto.configDataSource = param.getConfigDataSource();
+        mAtomsStorage.addSatelliteAccessControllerStats(proto);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
index d400c22..3f24968 100644
--- a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
+++ b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
@@ -21,6 +21,7 @@
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_CS;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_UNKNOWN;
+import static com.android.internal.telephony.flags.Flags.dataRatMetricEnabled;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,6 +32,7 @@
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.ServiceState;
 import android.telephony.ServiceState.RoamingType;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 
@@ -38,12 +40,14 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.TelephonyStatsLog;
 import com.android.internal.telephony.data.DataNetwork;
 import com.android.internal.telephony.data.DataNetworkController;
 import com.android.internal.telephony.data.DataNetworkController.DataNetworkControllerCallback;
 import com.android.internal.telephony.imsphone.ImsPhone;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
+import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.telephony.Rlog;
 
 import java.util.Set;
@@ -61,6 +65,8 @@
     private final PersistAtomsStorage mStorage;
     private final DeviceStateHelper mDeviceStateHelper;
     private boolean mExistAnyConnectedInternetPdn;
+    private int mCurrentDataRat =
+            TelephonyStatsLog.DATA_RAT_STATE_CHANGED__DATA_RAT__DATA_RAT_UNSPECIFIED;
 
     public ServiceStateStats(Phone phone) {
         super(Runnable::run);
@@ -118,6 +124,7 @@
             // Finish the duration of last service state and mark modem off
             addServiceState(mLastState.getAndSet(new TimestampedServiceState(null, now)), now);
         } else {
+            SatelliteController satelliteController = SatelliteController.getInstance();
             CellularServiceState newState = new CellularServiceState();
             newState.voiceRat = getVoiceRat(mPhone, serviceState);
             newState.dataRat = getRat(serviceState, NetworkRegistrationInfo.DOMAIN_PS);
@@ -135,11 +142,17 @@
             newState.overrideVoiceService = mOverrideVoiceService.get();
             newState.isDataEnabled = mPhone.getDataSettingsManager().isDataEnabled();
             newState.isIwlanCrossSim = isCrossSimCallingRegistered(mPhone);
+            newState.isNtn = satelliteController != null
+                    && satelliteController.isInSatelliteModeForCarrierRoaming(mPhone);
             TimestampedServiceState prevState =
                     mLastState.getAndSet(new TimestampedServiceState(newState, now));
             addServiceStateAndSwitch(
                     prevState, now, getDataServiceSwitch(prevState.mServiceState, newState));
         }
+
+        if (dataRatMetricEnabled()) {
+            writeDataRatAtom(serviceState);
+        }
     }
 
     /** Updates the fold state of the device for the current service state. */
@@ -306,6 +319,7 @@
         copy.overrideVoiceService = state.overrideVoiceService;
         copy.isDataEnabled = state.isDataEnabled;
         copy.isIwlanCrossSim = state.isIwlanCrossSim;
+        copy.isNtn = state.isNtn;
         return copy;
     }
 
@@ -456,6 +470,68 @@
                 || isNetworkRoaming(ss, NetworkRegistrationInfo.DOMAIN_PS);
     }
 
+    /** Collect data Rat metric. */
+    private void writeDataRatAtom(@NonNull ServiceState serviceState) {
+        if (DataConnectionStateTracker.getActiveDataSubId() != mPhone.getSubId()) {
+            return;
+        }
+        NetworkRegistrationInfo wwanRegInfo = serviceState.getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        if (wwanRegInfo == null) {
+            return;
+        }
+        int dataRat = wwanRegInfo.getAccessNetworkTechnology();
+        int nrFrequency = serviceState.getNrFrequencyRange();
+        int nrState = serviceState.getNrState();
+        int translatedDataRat =
+                TelephonyStatsLog.DATA_RAT_STATE_CHANGED__DATA_RAT__DATA_RAT_UNSPECIFIED;
+        if (!SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) {
+            translatedDataRat = TelephonyStatsLog.DATA_RAT_STATE_CHANGED__DATA_RAT__NO_SIM;
+        } else if (dataRat == TelephonyManager.NETWORK_TYPE_EHRPD
+                || dataRat == TelephonyManager.NETWORK_TYPE_HSPAP
+                || dataRat == TelephonyManager.NETWORK_TYPE_UMTS
+                || dataRat == TelephonyManager.NETWORK_TYPE_HSDPA
+                || dataRat == TelephonyManager.NETWORK_TYPE_HSUPA
+                || dataRat == TelephonyManager.NETWORK_TYPE_HSPA
+                || dataRat == TelephonyManager.NETWORK_TYPE_EVDO_0
+                || dataRat == TelephonyManager.NETWORK_TYPE_EVDO_A
+                || dataRat == TelephonyManager.NETWORK_TYPE_EVDO_B) {
+            translatedDataRat = TelephonyStatsLog.DATA_RAT_STATE_CHANGED__DATA_RAT__DATA_RAT_3G;
+        } else if (dataRat == TelephonyManager.NETWORK_TYPE_1xRTT
+                || dataRat == TelephonyManager.NETWORK_TYPE_GPRS
+                || dataRat == TelephonyManager.NETWORK_TYPE_EDGE
+                || dataRat == TelephonyManager.NETWORK_TYPE_CDMA
+                || dataRat == TelephonyManager.NETWORK_TYPE_GSM) {
+            translatedDataRat = TelephonyStatsLog.DATA_RAT_STATE_CHANGED__DATA_RAT__DATA_RAT_2G;
+        } else if (dataRat == TelephonyManager.NETWORK_TYPE_NR) {
+            translatedDataRat = nrFrequency != ServiceState.FREQUENCY_RANGE_MMWAVE
+                    ? TelephonyStatsLog.DATA_RAT_STATE_CHANGED__DATA_RAT__DATA_RAT_5G_SA_FR1 :
+                    TelephonyStatsLog.DATA_RAT_STATE_CHANGED__DATA_RAT__DATA_RAT_5G_SA_FR2;
+        } else if (dataRat == TelephonyManager.NETWORK_TYPE_LTE) {
+            if (nrState == NetworkRegistrationInfo.NR_STATE_CONNECTED) {
+                translatedDataRat = nrFrequency != ServiceState.FREQUENCY_RANGE_MMWAVE
+                    ? TelephonyStatsLog.DATA_RAT_STATE_CHANGED__DATA_RAT__DATA_RAT_5G_NSA_FR1 :
+                    TelephonyStatsLog.DATA_RAT_STATE_CHANGED__DATA_RAT__DATA_RAT_5G_NSA_FR2;
+            } else if (nrState == NetworkRegistrationInfo.NR_STATE_NOT_RESTRICTED) {
+                translatedDataRat =
+                        TelephonyStatsLog.DATA_RAT_STATE_CHANGED__DATA_RAT__DATA_RAT_5G_NSA_LTE;
+            } else {
+                translatedDataRat =
+                        TelephonyStatsLog.DATA_RAT_STATE_CHANGED__DATA_RAT__DATA_RAT_4G_LTE;
+            }
+        }
+
+        if (translatedDataRat != mCurrentDataRat) {
+            TelephonyStatsLog.write(TelephonyStatsLog.DATA_RAT_STATE_CHANGED, translatedDataRat);
+            mCurrentDataRat = translatedDataRat;
+        }
+    }
+
+    int getCurrentDataRat() {
+        return mCurrentDataRat;
+    }
+
     @VisibleForTesting
     protected long getTimeMillis() {
         return SystemClock.elapsedRealtime();
diff --git a/src/java/com/android/internal/telephony/metrics/SmsStats.java b/src/java/com/android/internal/telephony/metrics/SmsStats.java
index 949b72e..b62114c 100644
--- a/src/java/com/android/internal/telephony/metrics/SmsStats.java
+++ b/src/java/com/android/internal/telephony/metrics/SmsStats.java
@@ -57,9 +57,11 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.flags.Flags;
 import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
+import com.android.internal.telephony.satellite.metrics.CarrierRoamingSatelliteSessionStats;
 import com.android.telephony.Rlog;
 
 import java.util.Objects;
@@ -87,8 +89,9 @@
     }
 
     /** Create a new atom when multi-part incoming SMS is dropped due to missing parts. */
-    public void onDroppedIncomingMultipartSms(boolean is3gpp2, int receivedCount, int totalCount) {
-        IncomingSms proto = getIncomingDefaultProto(is3gpp2, SOURCE_NOT_INJECTED);
+    public void onDroppedIncomingMultipartSms(boolean is3gpp2, int receivedCount, int totalCount,
+            boolean isEmergency) {
+        IncomingSms proto = getIncomingDefaultProto(is3gpp2, SOURCE_NOT_INJECTED, isEmergency);
         // Keep SMS tech as unknown because it's possible that it changed overtime and is not
         // necessarily the current one. Similarly mark the RAT as unknown.
         proto.smsTech = INCOMING_SMS__SMS_TECH__SMS_TECH_UNKNOWN;
@@ -102,21 +105,21 @@
     /** Create a new atom when an SMS for the voicemail indicator is received. */
     public void onIncomingSmsVoicemail(boolean is3gpp2,
             @InboundSmsHandler.SmsSource int smsSource) {
-        IncomingSms proto = getIncomingDefaultProto(is3gpp2, smsSource);
+        IncomingSms proto = getIncomingDefaultProto(is3gpp2, smsSource, false);
         proto.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_VOICEMAIL_INDICATION;
         mAtomsStorage.addIncomingSms(proto);
     }
 
     /** Create a new atom when an SMS of type zero is received. */
     public void onIncomingSmsTypeZero(@InboundSmsHandler.SmsSource int smsSource) {
-        IncomingSms proto = getIncomingDefaultProto(false /* is3gpp2 */, smsSource);
+        IncomingSms proto = getIncomingDefaultProto(false /* is3gpp2 */, smsSource, false);
         proto.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_ZERO;
         mAtomsStorage.addIncomingSms(proto);
     }
 
     /** Create a new atom when an SMS-PP for the SIM card is received. */
     public void onIncomingSmsPP(@InboundSmsHandler.SmsSource int smsSource, boolean success) {
-        IncomingSms proto = getIncomingDefaultProto(false /* is3gpp2 */, smsSource);
+        IncomingSms proto = getIncomingDefaultProto(false /* is3gpp2 */, smsSource, false);
         proto.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_SMS_PP;
         proto.error = getIncomingSmsError(success);
         mAtomsStorage.addIncomingSms(proto);
@@ -125,8 +128,8 @@
     /** Create a new atom when an SMS is received successfully. */
     public void onIncomingSmsSuccess(boolean is3gpp2,
             @InboundSmsHandler.SmsSource int smsSource, int messageCount,
-            boolean blocked, long messageId) {
-        IncomingSms proto = getIncomingDefaultProto(is3gpp2, smsSource);
+            boolean blocked, long messageId, boolean isEmergency) {
+        IncomingSms proto = getIncomingDefaultProto(is3gpp2, smsSource, isEmergency);
         proto.totalParts = messageCount;
         proto.receivedParts = messageCount;
         proto.blocked = blocked;
@@ -136,16 +139,16 @@
 
     /** Create a new atom when an incoming SMS has an error. */
     public void onIncomingSmsError(boolean is3gpp2,
-            @InboundSmsHandler.SmsSource int smsSource, int result) {
-        IncomingSms proto = getIncomingDefaultProto(is3gpp2, smsSource);
+            @InboundSmsHandler.SmsSource int smsSource, int result, boolean isEmergency) {
+        IncomingSms proto = getIncomingDefaultProto(is3gpp2, smsSource, isEmergency);
         proto.error = getIncomingSmsError(result);
         mAtomsStorage.addIncomingSms(proto);
     }
 
     /** Create a new atom when an incoming WAP_PUSH SMS is received. */
     public void onIncomingSmsWapPush(@InboundSmsHandler.SmsSource int smsSource,
-            int messageCount, int result, long messageId) {
-        IncomingSms proto = getIncomingDefaultProto(false, smsSource);
+            int messageCount, int result, long messageId, boolean isEmergency) {
+        IncomingSms proto = getIncomingDefaultProto(false, smsSource, isEmergency);
         proto.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_WAP_PUSH;
         proto.totalParts = messageCount;
         proto.receivedParts = messageCount;
@@ -157,18 +160,18 @@
     /** Create a new atom when an outgoing SMS is sent. */
     public void onOutgoingSms(boolean isOverIms, boolean is3gpp2, boolean fallbackToCs,
             @SmsManager.Result int sendErrorCode, long messageId, boolean isFromDefaultApp,
-            long intervalMillis) {
+            long intervalMillis, boolean isEmergency) {
         onOutgoingSms(isOverIms, is3gpp2, fallbackToCs, sendErrorCode, NO_ERROR_CODE,
-                messageId, isFromDefaultApp, intervalMillis);
+                messageId, isFromDefaultApp, intervalMillis, isEmergency);
     }
 
     /** Create a new atom when an outgoing SMS is sent. */
     public void onOutgoingSms(boolean isOverIms, boolean is3gpp2, boolean fallbackToCs,
             @SmsManager.Result int sendErrorCode, int networkErrorCode, long messageId,
-            boolean isFromDefaultApp, long intervalMillis) {
+            boolean isFromDefaultApp, long intervalMillis, boolean isEmergency) {
         OutgoingSms proto =
                 getOutgoingDefaultProto(is3gpp2, isOverIms, messageId, isFromDefaultApp,
-                        intervalMillis);
+                        intervalMillis, isEmergency);
 
         // The field errorCode is used for up-to-Android-13 devices. From Android 14, sendErrorCode
         // and networkErrorCode will be used. The field errorCode will be deprecated when most
@@ -201,6 +204,9 @@
         proto.networkErrorCode = networkErrorCode;
 
         mAtomsStorage.addOutgoingSms(proto);
+        CarrierRoamingSatelliteSessionStats sessionStats =
+                CarrierRoamingSatelliteSessionStats.getInstance(mPhone.getSubId());
+        sessionStats.onOutgoingSms(mPhone.getSubId());
     }
 
     /** Create a new atom when user attempted to send an outgoing short code sms. */
@@ -214,7 +220,7 @@
 
     /** Creates a proto for a normal single-part {@code IncomingSms} with default values. */
     private IncomingSms getIncomingDefaultProto(boolean is3gpp2,
-            @InboundSmsHandler.SmsSource int smsSource) {
+            @InboundSmsHandler.SmsSource int smsSource, boolean isEmergency) {
         IncomingSms proto = new IncomingSms();
         proto.smsFormat = getSmsFormat(is3gpp2);
         proto.smsTech = getSmsTech(smsSource, is3gpp2);
@@ -234,12 +240,14 @@
         proto.messageId = RANDOM.nextLong();
         proto.count = 1;
         proto.isManagedProfile = mPhone.isManagedProfile();
+        proto.isNtn = isNonTerrestrialNetwork();
+        proto.isEmergency = isEmergency;
         return proto;
     }
 
     /** Create a proto for a normal {@code OutgoingSms} with default values. */
     private OutgoingSms getOutgoingDefaultProto(boolean is3gpp2, boolean isOverIms,
-            long messageId, boolean isFromDefaultApp, long intervalMillis) {
+            long messageId, boolean isFromDefaultApp, long intervalMillis, boolean isEmergency) {
         OutgoingSms proto = new OutgoingSms();
         proto.smsFormat = getSmsFormat(is3gpp2);
         proto.smsTech = getSmsTech(isOverIms, is3gpp2);
@@ -260,6 +268,8 @@
         proto.intervalMillis = intervalMillis;
         proto.count = 1;
         proto.isManagedProfile = mPhone.isManagedProfile();
+        proto.isEmergency = isEmergency;
+        proto.isNtn = isNonTerrestrialNetwork();
         return proto;
     }
 
@@ -397,6 +407,20 @@
         return phone.getCarrierId();
     }
 
+    private boolean isNonTerrestrialNetwork() {
+        if (!Flags.carrierEnabledSatelliteFlag()) {
+            return false;
+        }
+
+        ServiceState ss = getServiceState();
+        if (ss != null) {
+            return ss.isUsingNonTerrestrialNetwork();
+        } else {
+            Rlog.e(TAG, "isNonTerrestrialNetwork(), ServiceState is null");
+            return false;
+        }
+    }
+
     private void loge(String format, Object... args) {
         Rlog.e(TAG, "[" + mPhone.getPhoneId() + "]" + String.format(format, args));
     }
diff --git a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
index 9cf53c9..911424e 100644
--- a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
@@ -41,16 +41,19 @@
 import android.content.Context;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
+import android.os.PersistableBundle;
 import android.os.SystemClock;
 import android.telecom.VideoProfile;
 import android.telecom.VideoProfile.VideoState;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.AnomalyReporter;
+import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PreciseDataConnectionState;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyManager.CallComposerStatus;
 import android.telephony.data.ApnSetting;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsStreamMediaProfile;
@@ -75,6 +78,7 @@
 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
 import com.android.internal.telephony.nano.TelephonyProto.TelephonyCallSession.Event.AudioCodec;
+import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.internal.telephony.uicc.UiccController;
 import com.android.telephony.Rlog;
 
@@ -167,15 +171,16 @@
     private final UiccController mUiccController = UiccController.getInstance();
     private final DeviceStateHelper mDeviceStateHelper =
             PhoneFactory.getMetricsCollector().getDeviceStateHelper();
-
     private final VonrHelper mVonrHelper =
             PhoneFactory.getMetricsCollector().getVonrHelper();
+    private final SatelliteController mSatelliteController;
 
     public VoiceCallSessionStats(int phoneId, Phone phone, @NonNull FeatureFlags featureFlags) {
         mPhoneId = phoneId;
         mPhone = phone;
         mFlags = featureFlags;
         DataConnectionStateTracker.getInstance(phoneId).start(phone);
+        mSatelliteController = SatelliteController.getInstance();
     }
 
     /* CS calls */
@@ -570,6 +575,13 @@
             proto.vonrEnabled = mVonrHelper.getVonrEnabled(mPhone.getSubId());
         }
 
+        proto.supportsBusinessCallComposer = isBusinessCallSupported();
+        // 0 is defined as UNKNOWN in Enum
+        proto.callComposerStatus = getCallComposerStatusForPhone() + 1;
+
+        proto.isNtn = mSatelliteController != null
+                ? mSatelliteController.isInSatelliteModeForCarrierRoaming(mPhone) : false;
+
         mAtomsStorage.addVoiceCallSession(proto);
 
         // merge RAT usages to PersistPullers when the call session ends (i.e. no more active calls)
@@ -790,11 +802,8 @@
                 }
             }
         }
-        if (bearer == VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS) {
-            return TelephonyManager.NETWORK_TYPE_UNKNOWN;
-        } else {
-            return ServiceStateStats.getRat(state, NetworkRegistrationInfo.DOMAIN_CS);
-        }
+
+        return ServiceStateStats.getRat(state, NetworkRegistrationInfo.DOMAIN_CS);
     }
 
     /** Resets the list of codecs used for the connection with only the codec currently in use. */
@@ -943,6 +952,36 @@
         return false;
     }
 
+    private @CallComposerStatus int getCallComposerStatusForPhone() {
+        TelephonyManager telephonyManager = mPhone.getContext()
+                .getSystemService(TelephonyManager.class);
+        if (telephonyManager == null) {
+            return TelephonyManager.CALL_COMPOSER_STATUS_OFF;
+        }
+        telephonyManager = telephonyManager.createForSubscriptionId(mPhone.getSubId());
+        return telephonyManager.getCallComposerStatus();
+    }
+
+    private boolean isBusinessCallSupported() {
+        CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
+                mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (carrierConfigManager == null) {
+            return false;
+        }
+        int subId = mPhone.getSubId();
+        PersistableBundle b = null;
+        try {
+            b = carrierConfigManager.getConfigForSubId(subId,
+                    CarrierConfigManager.KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL);
+        } catch (RuntimeException e) {
+            loge("CarrierConfigLoader is not available.");
+        }
+        if (b == null || b.isEmpty()) {
+            return false;
+        }
+        return b.getBoolean(CarrierConfigManager.KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL);
+    }
+
     @VisibleForTesting
     protected long getTimeMillis() {
         return SystemClock.elapsedRealtime();
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramController.java b/src/java/com/android/internal/telephony/satellite/DatagramController.java
index 877eaf1..ff2ee9f 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramController.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramController.java
@@ -16,16 +16,26 @@
 
 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;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_IDLE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.Resources;
 import android.os.Build;
 import android.os.Looper;
 import android.os.SystemProperties;
+import android.telephony.DropBoxManagerLoggerBackend;
+import android.telephony.PersistentLogger;
 import android.telephony.Rlog;
 import android.telephony.satellite.ISatelliteDatagramCallback;
 import android.telephony.satellite.SatelliteDatagram;
@@ -34,7 +44,10 @@
 import com.android.internal.R;
 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.List;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 
@@ -46,6 +59,7 @@
 
     @NonNull private static DatagramController sInstance;
     @NonNull private final Context mContext;
+    @NonNull private final FeatureFlags mFeatureFlags;
     @NonNull private final PointingAppController mPointingAppController;
     @NonNull private final DatagramDispatcher mDatagramDispatcher;
     @NonNull private final DatagramReceiver mDatagramReceiver;
@@ -59,6 +73,10 @@
     public static final int TIMEOUT_TYPE_DATAGRAM_WAIT_FOR_CONNECTED_STATE = 2;
     /** This type is used by CTS to override the time to wait for response of the send request */
     public static final int TIMEOUT_TYPE_WAIT_FOR_DATAGRAM_SENDING_RESPONSE = 3;
+    /** This type is used by CTS to override the time to datagram delay in demo mode */
+    public static final int TIMEOUT_TYPE_DATAGRAM_DELAY_IN_DEMO_MODE = 4;
+    /** This type is used by CTS to override wait for device alignment in demo datagram boolean */
+    public static final int BOOLEAN_TYPE_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_DATAGRAM = 1;
     private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
     private static final boolean DEBUG = !"user".equals(Build.TYPE);
 
@@ -67,8 +85,10 @@
     @GuardedBy("mLock")
     private int mSendSubId;
     @GuardedBy("mLock")
+    private @SatelliteManager.DatagramType int mDatagramType = DATAGRAM_TYPE_UNKNOWN;
+    @GuardedBy("mLock")
     private @SatelliteManager.SatelliteDatagramTransferState int mSendDatagramTransferState =
-            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
+            SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
     @GuardedBy("mLock")
     private int mSendPendingCount = 0;
     @GuardedBy("mLock")
@@ -78,20 +98,24 @@
     private int mReceiveSubId;
     @GuardedBy("mLock")
     private @SatelliteManager.SatelliteDatagramTransferState int mReceiveDatagramTransferState =
-            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
+            SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
     @GuardedBy("mLock")
     private int mReceivePendingCount = 0;
     @GuardedBy("mLock")
     private int mReceiveErrorCode = SatelliteManager.SATELLITE_RESULT_SUCCESS;
-
-    private SatelliteDatagram mDemoModeDatagram;
+    @GuardedBy("mLock")
+    private final List<SatelliteDatagram> mDemoModeDatagramList;
     private boolean mIsDemoMode = false;
     private long mAlignTimeoutDuration = SATELLITE_ALIGN_TIMEOUT;
     private long mDatagramWaitTimeForConnectedState;
     private long mModemImageSwitchingDuration;
+    private boolean mWaitForDeviceAlignmentInDemoDatagram;
+    private long mDatagramWaitTimeForConnectedStateForLastMessage;
     @GuardedBy("mLock")
     @SatelliteManager.SatelliteModemState
     private int mSatelltieModemState = SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN;
+    @Nullable
+    private PersistentLogger mPersistentLogger = null;
 
     /**
      * @return The singleton instance of DatagramController.
@@ -107,14 +131,17 @@
      * Create the DatagramController singleton instance.
      * @param context The Context to use to create the DatagramController.
      * @param looper The looper for the handler.
+     * @param featureFlags The telephony feature flags.
      * @param pointingAppController PointingAppController is used to update
      *                              PointingApp about datagram transfer state changes.
      * @return The singleton instance of DatagramController.
      */
     public static DatagramController make(@NonNull Context context, @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags,
             @NonNull PointingAppController pointingAppController) {
         if (sInstance == null) {
-            sInstance = new DatagramController(context, looper, pointingAppController);
+            sInstance = new DatagramController(
+                    context, looper, featureFlags, pointingAppController);
         }
         return sInstance;
     }
@@ -124,25 +151,40 @@
      *
      * @param context The Context for the DatagramController.
      * @param looper The looper for the handler
+     * @param featureFlags The telephony feature flags.
      * @param pointingAppController PointingAppController is used to update PointingApp
      *                              about datagram transfer state changes.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    protected DatagramController(@NonNull Context context, @NonNull Looper  looper,
+    public DatagramController(@NonNull Context context, @NonNull Looper  looper,
+            @NonNull FeatureFlags featureFlags,
             @NonNull PointingAppController pointingAppController) {
         mContext = context;
+        mFeatureFlags = featureFlags;
         mPointingAppController = pointingAppController;
 
         // Create the DatagramDispatcher singleton,
         // which is used to send satellite datagrams.
-        mDatagramDispatcher = DatagramDispatcher.make(mContext, looper, this);
+        mDatagramDispatcher = DatagramDispatcher.make(
+                mContext, looper, mFeatureFlags, this);
 
         // Create the DatagramReceiver singleton,
         // which is used to receive satellite datagrams.
-        mDatagramReceiver = DatagramReceiver.make(mContext, looper, this);
+        mDatagramReceiver = DatagramReceiver.make(
+                mContext, looper, mFeatureFlags, this);
 
         mDatagramWaitTimeForConnectedState = getDatagramWaitForConnectedStateTimeoutMillis();
         mModemImageSwitchingDuration = getSatelliteModemImageSwitchingDurationMillis();
+        mWaitForDeviceAlignmentInDemoDatagram =
+                getWaitForDeviceAlignmentInDemoDatagramFromResources();
+        mDatagramWaitTimeForConnectedStateForLastMessage =
+                getDatagramWaitForConnectedStateForLastMessageTimeoutMillis();
+        mDemoModeDatagramList = new ArrayList<>();
+
+        if (isSatellitePersistentLoggingEnabled(context, featureFlags)) {
+            mPersistentLogger = new PersistentLogger(
+                    DropBoxManagerLoggerBackend.getInstance(context));
+        }
     }
 
     /**
@@ -183,6 +225,7 @@
      * @param callback The callback to get {@link SatelliteManager.SatelliteResult} of the request.
      */
     public void pollPendingSatelliteDatagrams(int subId, @NonNull Consumer<Integer> callback) {
+        plogd("pollPendingSatelliteDatagrams");
         mDatagramReceiver.pollPendingSatelliteDatagrams(subId, callback);
     }
 
@@ -208,7 +251,6 @@
     public void sendSatelliteDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
             @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
             @NonNull Consumer<Integer> callback) {
-        setDemoModeDatagram(datagramType, datagram);
         mDatagramDispatcher.sendSatelliteDatagram(subId, datagramType, datagram,
                 needFullScreenPointingUI, callback);
     }
@@ -221,23 +263,43 @@
      * @param sendPendingCount number of datagrams that are currently being sent
      * @param errorCode If datagram transfer failed, the reason for failure.
      */
-    public void updateSendStatus(int subId,
+    public void updateSendStatus(int subId, @SatelliteManager.DatagramType int datagramType,
             @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState,
             int sendPendingCount, int errorCode) {
         synchronized (mLock) {
-            logd("updateSendStatus"
+            plogd("updateSendStatus"
                     + " subId: " + subId
+                    + " datagramType: " + datagramType
                     + " datagramTransferState: " + datagramTransferState
                     + " sendPendingCount: " + sendPendingCount + " errorCode: " + errorCode);
+            if (shouldSuppressDatagramTransferStateUpdate(datagramType)) {
+                plogd("Ignore the request to update send status");
+                return;
+            }
 
             mSendSubId = subId;
+            mDatagramType = datagramType;
             mSendDatagramTransferState = datagramTransferState;
             mSendPendingCount = sendPendingCount;
             mSendErrorCode = errorCode;
-
             notifyDatagramTransferStateChangedToSessionController();
-            mPointingAppController.updateSendDatagramTransferState(mSendSubId,
+            mPointingAppController.updateSendDatagramTransferState(mSendSubId, mDatagramType,
                     mSendDatagramTransferState, mSendPendingCount, mSendErrorCode);
+            retryPollPendingDatagramsInDemoMode();
+        }
+    }
+
+    private boolean shouldSuppressDatagramTransferStateUpdate(
+            @SatelliteManager.DatagramType int datagramType) {
+        synchronized (mLock) {
+            if (!SatelliteController.getInstance().isSatelliteAttachRequired()) {
+                return false;
+            }
+            if (datagramType == DATAGRAM_TYPE_KEEP_ALIVE
+                    && mSatelltieModemState == SATELLITE_MODEM_STATE_NOT_CONNECTED) {
+                return true;
+            }
+            return false;
         }
     }
 
@@ -253,7 +315,7 @@
             @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState,
             int receivePendingCount, int errorCode) {
         synchronized (mLock) {
-            logd("updateReceiveStatus"
+            plogd("updateReceiveStatus"
                     + " subId: " + subId
                     + " datagramTransferState: " + datagramTransferState
                     + " receivePendingCount: " + receivePendingCount + " errorCode: " + errorCode);
@@ -266,6 +328,7 @@
             notifyDatagramTransferStateChangedToSessionController();
             mPointingAppController.updateReceiveDatagramTransferState(mReceiveSubId,
                     mReceiveDatagramTransferState, mReceivePendingCount, mReceiveErrorCode);
+            retryPollPendingDatagramsInDemoMode();
         }
 
         if (isPollingInIdleState()) {
@@ -295,9 +358,16 @@
         mDatagramReceiver.onSatelliteModemStateChanged(state);
     }
 
-    void setDeviceAlignedWithSatellite(boolean isAligned) {
+    /**
+     * Set whether the device is aligned with the satellite.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void setDeviceAlignedWithSatellite(boolean isAligned) {
         mDatagramDispatcher.setDeviceAlignedWithSatellite(isAligned);
         mDatagramReceiver.setDeviceAlignedWithSatellite(isAligned);
+        if (isAligned) {
+            retryPollPendingDatagramsInDemoMode();
+        }
     }
 
     @VisibleForTesting
@@ -313,10 +383,17 @@
      * before transferring datagrams via satellite.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public boolean needsWaitingForSatelliteConnected() {
+    public boolean needsWaitingForSatelliteConnected(
+            @SatelliteManager.DatagramType int datagramType) {
         synchronized (mLock) {
-            if (SatelliteController.getInstance().isSatelliteAttachRequired()
-                    && mSatelltieModemState != SATELLITE_MODEM_STATE_CONNECTED
+            if (!SatelliteController.getInstance().isSatelliteAttachRequired()) {
+                return false;
+            }
+            if (datagramType == DATAGRAM_TYPE_KEEP_ALIVE
+                    && mSatelltieModemState == SATELLITE_MODEM_STATE_NOT_CONNECTED) {
+                return false;
+            }
+            if (mSatelltieModemState != SATELLITE_MODEM_STATE_CONNECTED
                     && mSatelltieModemState != SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING) {
                 return true;
             }
@@ -327,14 +404,14 @@
     public boolean isSendingInIdleState() {
         synchronized (mLock) {
             return (mSendDatagramTransferState
-                    == SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                    == SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
         }
     }
 
     public boolean isPollingInIdleState() {
         synchronized (mLock) {
             return (mReceiveDatagramTransferState
-                    == SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+                    == SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
         }
     }
 
@@ -349,26 +426,42 @@
         mDatagramReceiver.setDemoMode(isDemoMode);
 
         if (!isDemoMode) {
-            mDemoModeDatagram = null;
+            synchronized (mLock) {
+                mDemoModeDatagramList.clear();
+            }
+            setDeviceAlignedWithSatellite(false);
         }
+        plogd("setDemoMode: mIsDemoMode=" + mIsDemoMode);
     }
 
     /** Get the last sent datagram for demo mode */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public SatelliteDatagram getDemoModeDatagram() {
-        return mDemoModeDatagram;
+    public SatelliteDatagram popDemoModeDatagram() {
+        if (!mIsDemoMode) {
+            return null;
+        }
+
+        synchronized (mLock) {
+            plogd("popDemoModeDatagram");
+            return mDemoModeDatagramList.size() > 0 ? mDemoModeDatagramList.remove(0) : null;
+        }
     }
 
     /**
      * Set last sent datagram for demo mode
-     * @param datagramType datagram type, only DATAGRAM_TYPE_SOS_MESSAGE will be saved
+     * @param datagramType datagram type, DATAGRAM_TYPE_SOS_MESSAGE,
+     *                     DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
+     *                     DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED will be saved
      * @param datagram datagram The last datagram saved when sendSatelliteDatagramForDemo is called
      */
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    protected void setDemoModeDatagram(@SatelliteManager.DatagramType int datagramType,
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void pushDemoModeDatagram(@SatelliteManager.DatagramType int datagramType,
             SatelliteDatagram datagram) {
-        if (mIsDemoMode &&  datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
-            mDemoModeDatagram = datagram;
+        if (mIsDemoMode && SatelliteServiceUtils.isSosMessage(datagramType)) {
+            synchronized (mLock) {
+                mDemoModeDatagramList.add(datagram);
+                plogd("pushDemoModeDatagram size=" + mDemoModeDatagramList.size());
+            }
         }
     }
 
@@ -377,13 +470,17 @@
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    public long getDatagramWaitTimeForConnectedState() {
+    public long getDatagramWaitTimeForConnectedState(boolean isLastSosMessage) {
         synchronized (mLock) {
+            long timeout = isLastSosMessage ? mDatagramWaitTimeForConnectedStateForLastMessage :
+                    mDatagramWaitTimeForConnectedState;
+            logd("getDatagramWaitTimeForConnectedState: isLastSosMessage=" + isLastSosMessage
+                    + ", timeout=" + timeout + ", modemState=" + mSatelltieModemState);
             if (mSatelltieModemState == SATELLITE_MODEM_STATE_OFF
                     || mSatelltieModemState == SATELLITE_MODEM_STATE_IDLE) {
-                return (mDatagramWaitTimeForConnectedState + mModemImageSwitchingDuration);
+                return (timeout + mModemImageSwitchingDuration);
             }
-            return mDatagramWaitTimeForConnectedState;
+            return timeout;
         }
     }
 
@@ -396,11 +493,11 @@
     boolean setDatagramControllerTimeoutDuration(
             boolean reset, int timeoutType, long timeoutMillis) {
         if (!isMockModemAllowed()) {
-            loge("Updating timeout duration is not allowed");
+            ploge("Updating timeout duration is not allowed");
             return false;
         }
 
-        logd("setDatagramControllerTimeoutDuration: timeoutMillis=" + timeoutMillis
+        plogd("setDatagramControllerTimeoutDuration: timeoutMillis=" + timeoutMillis
                 + ", reset=" + reset + ", timeoutType=" + timeoutType);
         if (timeoutType == TIMEOUT_TYPE_ALIGN) {
             if (reset) {
@@ -419,8 +516,40 @@
             }
         } else if (timeoutType == TIMEOUT_TYPE_WAIT_FOR_DATAGRAM_SENDING_RESPONSE) {
             mDatagramDispatcher.setWaitTimeForDatagramSendingResponse(reset, timeoutMillis);
+        } else if (timeoutType == TIMEOUT_TYPE_DATAGRAM_DELAY_IN_DEMO_MODE) {
+            mDatagramDispatcher.setTimeoutDatagramDelayInDemoMode(reset, timeoutMillis);
         } else {
-            loge("Invalid timeout type " + timeoutType);
+            ploge("Invalid timeout type " + timeoutType);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * This API can be used by only CTS to override the boolean configs used by the
+     * DatagramController module.
+     *
+     * @param enable Whether to enable or disable boolean config.
+     * @return {@code true} if the boolean config is set successfully, {@code false} otherwise.
+     */
+    boolean setDatagramControllerBooleanConfig(
+            boolean reset, int booleanType, boolean enable) {
+        if (!isMockModemAllowed()) {
+            loge("Updating boolean config is not allowed");
+            return false;
+        }
+
+        logd("setDatagramControllerTimeoutDuration: booleanType=" + booleanType
+                + ", reset=" + reset + ", enable=" + enable);
+        if (booleanType == BOOLEAN_TYPE_WAIT_FOR_DEVICE_ALIGNMENT_IN_DEMO_DATAGRAM) {
+            if (reset) {
+                mWaitForDeviceAlignmentInDemoDatagram =
+                        getWaitForDeviceAlignmentInDemoDatagramFromResources();
+            } else {
+                mWaitForDeviceAlignmentInDemoDatagram = enable;
+            }
+        } else {
+            loge("Invalid boolean type " + booleanType);
             return false;
         }
         return true;
@@ -433,7 +562,7 @@
     private void notifyDatagramTransferStateChangedToSessionController() {
         SatelliteSessionController sessionController = SatelliteSessionController.getInstance();
         if (sessionController == null) {
-            loge("notifyDatagramTransferStateChangeToSessionController: SatelliteSessionController"
+            ploge("notifyDatagramTransferStateChangeToSessionController: SatelliteSessionController"
                     + " is not initialized yet");
         } else {
             sessionController.onDatagramTransferStateChanged(
@@ -451,6 +580,11 @@
                 R.integer.config_satellite_modem_image_switching_duration_millis);
     }
 
+    private long getDatagramWaitForConnectedStateForLastMessageTimeoutMillis() {
+        return mContext.getResources().getInteger(
+                R.integer.config_datagram_wait_for_connected_state_for_last_message_timeout_millis);
+    }
+
     /**
      * This API can be used by only CTS to override the cached value for the device overlay config
      * value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether
@@ -463,6 +597,55 @@
         mDatagramDispatcher.setShouldSendDatagramToModemInDemoMode(shouldSendToModemInDemoMode);
     }
 
+    private void retryPollPendingDatagramsInDemoMode() {
+        synchronized (mLock) {
+            if (mIsDemoMode && isSendingInIdleState() && isPollingInIdleState()
+                    && !mDemoModeDatagramList.isEmpty()) {
+                Consumer<Integer> internalCallback = new Consumer<Integer>() {
+                    @Override
+                    public void accept(Integer result) {
+                        if (result != SATELLITE_RESULT_SUCCESS) {
+                            plogd("retryPollPendingDatagramsInDemoMode result: " + result);
+                        }
+                    }
+                };
+                pollPendingSatelliteDatagrams(DEFAULT_SUBSCRIPTION_ID, internalCallback);
+            }
+        }
+    }
+
+    /**
+     * Get whether to wait for device alignment with satellite before sending datagrams.
+     *
+     * @param isAligned if the device is aligned with satellite or not
+     * @return {@code true} if device is not aligned to satellite,
+     * and it is required to wait for alignment else {@code false}
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean waitForAligningToSatellite(boolean isAligned) {
+        if (isAligned) {
+            return false;
+        }
+
+        return getWaitForDeviceAlignmentInDemoDatagram();
+    }
+
+    private boolean getWaitForDeviceAlignmentInDemoDatagram() {
+        return mWaitForDeviceAlignmentInDemoDatagram;
+    }
+
+    private boolean getWaitForDeviceAlignmentInDemoDatagramFromResources() {
+        boolean waitForDeviceAlignmentInDemoDatagram = false;
+        try {
+            waitForDeviceAlignmentInDemoDatagram = mContext.getResources().getBoolean(
+                    R.bool.config_wait_for_device_alignment_in_demo_datagram);
+        } catch (Resources.NotFoundException ex) {
+            loge("getWaitForDeviceAlignmentInDemoDatagram: ex=" + ex);
+        }
+
+        return waitForDeviceAlignmentInDemoDatagram;
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
@@ -470,4 +653,31 @@
     private static void loge(@NonNull String log) {
         Rlog.e(TAG, log);
     }
+
+    private boolean isSatellitePersistentLoggingEnabled(
+            @NonNull Context context, @NonNull FeatureFlags featureFlags) {
+        if (featureFlags.satellitePersistentLogging()) {
+            return true;
+        }
+        try {
+            return context.getResources().getBoolean(
+                    R.bool.config_dropboxmanager_persistent_logging_enabled);
+        } catch (RuntimeException e) {
+            return false;
+        }
+    }
+
+    private void plogd(@NonNull String log) {
+        Rlog.d(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.debug(TAG, log);
+        }
+    }
+
+    private void ploge(@NonNull String log) {
+        Rlog.e(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.error(TAG, log);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
index 5cac1dd..2c9463f 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramDispatcher.java
@@ -16,8 +16,10 @@
 
 package com.android.internal.telephony.satellite;
 
+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_RESULT_MODEM_TIMEOUT;
+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;
@@ -30,6 +32,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.telephony.DropBoxManagerLoggerBackend;
+import android.telephony.PersistentLogger;
 import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.satellite.SatelliteDatagram;
@@ -39,12 +43,15 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.SatelliteStats;
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
+import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
 
 import java.util.LinkedHashMap;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
@@ -61,11 +68,14 @@
     private static final int EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT = 4;
     private static final int EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT = 5;
     private static final int EVENT_ABORT_SENDING_SATELLITE_DATAGRAMS_DONE = 6;
-
+    private static final int EVENT_WAIT_FOR_SIMULATED_POLL_DATAGRAMS_DELAY_TIMED_OUT = 7;
+    private static final Long TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE = TimeUnit.SECONDS.toMillis(10);
     @NonNull private static DatagramDispatcher sInstance;
     @NonNull private final Context mContext;
     @NonNull private final DatagramController mDatagramController;
     @NonNull private final ControllerMetricsStats mControllerMetricsStats;
+    @NonNull private final SessionMetricsStats mSessionMetricsStats;
+    @NonNull private final FeatureFlags mFeatureFlags;
 
     private boolean mIsDemoMode = false;
     private boolean mIsAligned = false;
@@ -76,6 +86,7 @@
     private AtomicBoolean mShouldSendDatagramToModemInDemoMode = null;
 
     private final Object mLock = new Object();
+    private long mDemoTimeoutDuration = TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE;
 
     @GuardedBy("mLock")
     private boolean mSendingDatagramInProgress;
@@ -97,18 +108,34 @@
             mPendingNonEmergencyDatagramsMap = new LinkedHashMap<>();
 
     private long mWaitTimeForDatagramSendingResponse;
+    private long mWaitTimeForDatagramSendingForLastMessageResponse;
+    @SatelliteManager.DatagramType
+    private int mLastSendRequestDatagramType = DATAGRAM_TYPE_UNKNOWN;
+    @Nullable private PersistentLogger mPersistentLogger = null;
 
     /**
      * Create the DatagramDispatcher singleton instance.
      * @param context The Context to use to create the DatagramDispatcher.
      * @param looper The looper for the handler.
+     * @param featureFlags The telephony feature flags.
      * @param datagramController DatagramController which is used to update datagram transfer state.
      * @return The singleton instance of DatagramDispatcher.
      */
     public static DatagramDispatcher make(@NonNull Context context, @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags,
             @NonNull DatagramController datagramController) {
         if (sInstance == null) {
-            sInstance = new DatagramDispatcher(context, looper, datagramController);
+            sInstance = new DatagramDispatcher(context, looper, featureFlags, datagramController);
+        }
+        return sInstance;
+    }
+
+    /**
+     * @return The singleton instance of DatagramDispatcher.
+     */
+    public static DatagramDispatcher getInstance() {
+        if (sInstance == null) {
+            loge("DatagramDispatcher was not yet initialized.");
         }
         return sInstance;
     }
@@ -118,20 +145,30 @@
      *
      * @param context The Context for the DatagramDispatcher.
      * @param looper The looper for the handler.
+     * @param featureFlags The telephony feature flags.
      * @param datagramController DatagramController which is used to update datagram transfer state.
      */
     @VisibleForTesting
     protected DatagramDispatcher(@NonNull Context context, @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags,
             @NonNull DatagramController datagramController) {
         super(looper);
         mContext = context;
+        mFeatureFlags = featureFlags;
         mDatagramController = datagramController;
         mControllerMetricsStats = ControllerMetricsStats.getInstance();
+        mSessionMetricsStats = SessionMetricsStats.getInstance();
+        if (isSatellitePersistentLoggingEnabled(context, featureFlags)) {
+            mPersistentLogger = new PersistentLogger(
+                    DropBoxManagerLoggerBackend.getInstance(context));
+        }
 
         synchronized (mLock) {
             mSendingDatagramInProgress = false;
         }
         mWaitTimeForDatagramSendingResponse = getWaitForDatagramSendingResponseTimeoutMillis();
+        mWaitTimeForDatagramSendingForLastMessageResponse =
+                getWaitForDatagramSendingResponseForLastMessageTimeoutMillis();
     }
 
     private static final class DatagramDispatcherHandlerRequest {
@@ -196,7 +233,9 @@
 
         switch(msg.what) {
             case CMD_SEND_SATELLITE_DATAGRAM: {
-                logd("CMD_SEND_SATELLITE_DATAGRAM");
+                plogd("CMD_SEND_SATELLITE_DATAGRAM mIsDemoMode=" + mIsDemoMode
+                        + ", shouldSendDatagramToModemInDemoMode="
+                        + shouldSendDatagramToModemInDemoMode());
                 request = (DatagramDispatcherHandlerRequest) msg.obj;
                 SendSatelliteDatagramArgument argument =
                         (SendSatelliteDatagramArgument) request.argument;
@@ -205,11 +244,11 @@
                 synchronized (mLock) {
                     if (mIsDemoMode && !shouldSendDatagramToModemInDemoMode()) {
                         AsyncResult.forMessage(onCompleted, SATELLITE_RESULT_SUCCESS, null);
-                        onCompleted.sendToTarget();
+                        sendMessageDelayed(onCompleted, getDemoTimeoutDuration());
                     } else {
                         SatelliteModemInterface.getInstance().sendSatelliteDatagram(
                                 argument.datagram,
-                                argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                                SatelliteServiceUtils.isSosMessage(argument.datagramType),
                                 argument.needFullScreenPointingUI, onCompleted);
                         startWaitForDatagramSendingResponseTimer(argument);
                     }
@@ -226,14 +265,16 @@
                 synchronized (mLock) {
                     if (mIsDemoMode && (error == SatelliteManager.SATELLITE_RESULT_SUCCESS)) {
                         if (argument.skipCheckingSatelliteAligned) {
-                            logd("Satellite was already aligned. No need to check alignment again");
-                        } else if (!mIsAligned) {
-                            logd("Satellite is not aligned in demo mode, wait for the alignment.");
+                            plogd("Satellite was already aligned. "
+                                + "No need to check alignment again");
+                        } else if (mDatagramController.waitForAligningToSatellite(mIsAligned)) {
+                            plogd("Satellite is not aligned in demo mode, wait for the alignment.");
                             startSatelliteAlignedTimer(request);
                             break;
                         }
                     }
-                    logd("EVENT_SEND_SATELLITE_DATAGRAM_DONE error: " + error);
+                    plogd("EVENT_SEND_SATELLITE_DATAGRAM_DONE error: " + error
+                            + ", mIsDemoMode=" + mIsDemoMode);
 
                     /*
                      * The response should be ignored if either of the following hold
@@ -243,7 +284,7 @@
                      * 3) All pending send requests have been aborted due to some error.
                      */
                     if (!shouldProcessEventSendSatelliteDatagramDone(argument)) {
-                        logw("The message " + argument.datagramId + " was already processed");
+                        plogw("The message " + argument.datagramId + " was already processed");
                         break;
                     }
 
@@ -253,48 +294,37 @@
                     // Log metrics about the outgoing datagram
                     reportSendDatagramCompleted(argument, error);
                     // Remove current datagram from pending map.
-                    if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+                    if (SatelliteServiceUtils.isSosMessage(argument.datagramType)) {
                         mPendingEmergencyDatagramsMap.remove(argument.datagramId);
                     } else {
                         mPendingNonEmergencyDatagramsMap.remove(argument.datagramId);
                     }
 
-                    if (error == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
+                    if (error == SATELLITE_RESULT_SUCCESS) {
                         // Update send status for current datagram
-                        mDatagramController.updateSendStatus(argument.subId,
+                        mDatagramController.updateSendStatus(argument.subId, argument.datagramType,
                                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS,
                                 getPendingDatagramCount(), error);
-                        mControllerMetricsStats.reportOutgoingDatagramSuccessCount(
-                                argument.datagramType);
-
-                        if (getPendingDatagramCount() > 0) {
-                            // Send response for current datagram
-                            argument.callback.accept(error);
-                            // Send pending datagrams
-                            sendPendingDatagrams();
-                        } else {
-                            mDatagramController.updateSendStatus(argument.subId,
-                                    SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                                    0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
-                            // Send response for current datagram
-                            argument.callback.accept(error);
-                        }
+                        startWaitForSimulatedPollDatagramsDelayTimer(request);
                     } else {
                         // Update send status
-                        mDatagramController.updateSendStatus(argument.subId,
+                        mDatagramController.updateSendStatus(argument.subId, argument.datagramType,
                                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
                                 getPendingDatagramCount(), error);
-                        mDatagramController.updateSendStatus(argument.subId,
-                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
-                                0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
+                    }
+
+                    if (getPendingDatagramCount() > 0) {
                         // Send response for current datagram
-                        // after updating datagram transfer state internally.
                         argument.callback.accept(error);
-                        // Abort sending all the pending datagrams
-                        mControllerMetricsStats.reportOutgoingDatagramFailCount(
-                                argument.datagramType);
-                        abortSendingPendingDatagrams(argument.subId,
-                                SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
+                        // Send pending datagrams
+                        sendPendingDatagrams();
+                    } else {
+                        mDatagramController.updateSendStatus(argument.subId,
+                                argument.datagramType,
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE, 0,
+                                SatelliteManager.SATELLITE_RESULT_SUCCESS);
+                        // Send response for current datagram
+                        argument.callback.accept(error);
                     }
                 }
                 break;
@@ -311,11 +341,18 @@
             }
 
             case EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT:
-                handleEventDatagramWaitForConnectedStateTimedOut();
+                handleEventDatagramWaitForConnectedStateTimedOut(
+                        (SendSatelliteDatagramArgument) msg.obj);
+                break;
+
+            case EVENT_WAIT_FOR_SIMULATED_POLL_DATAGRAMS_DELAY_TIMED_OUT:
+                request = (DatagramDispatcherHandlerRequest) msg.obj;
+                handleEventWaitForSimulatedPollDatagramsDelayTimedOut(
+                        (SendSatelliteDatagramArgument) request.argument);
                 break;
 
             default:
-                logw("DatagramDispatcherHandler: unexpected message code: " + msg.what);
+                plogw("DatagramDispatcherHandler: unexpected message code: " + msg.what);
                 break;
         }
     }
@@ -343,36 +380,36 @@
 
         long datagramId = mNextDatagramId.getAndUpdate(
                 n -> ((n + 1) % DatagramController.MAX_DATAGRAM_ID));
-
         SendSatelliteDatagramArgument datagramArgs =
                 new SendSatelliteDatagramArgument(subId, datagramId, datagramType, datagram,
                         needFullScreenPointingUI, callback);
+        mLastSendRequestDatagramType = datagramType;
 
         synchronized (mLock) {
             // Add datagram to pending datagram map
-            if (datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+            if (SatelliteServiceUtils.isSosMessage(datagramType)) {
                 mPendingEmergencyDatagramsMap.put(datagramId, datagramArgs);
             } else {
                 mPendingNonEmergencyDatagramsMap.put(datagramId, datagramArgs);
             }
 
-            if (mDatagramController.needsWaitingForSatelliteConnected()) {
-                logd("sendDatagram: wait for satellite connected");
-                mDatagramController.updateSendStatus(subId,
+            if (mDatagramController.needsWaitingForSatelliteConnected(datagramType)) {
+                plogd("sendDatagram: wait for satellite connected");
+                mDatagramController.updateSendStatus(subId, datagramType,
                         SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT,
                         getPendingDatagramCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
-                startDatagramWaitForConnectedStateTimer();
+                startDatagramWaitForConnectedStateTimer(datagramArgs);
             } else if (!mSendingDatagramInProgress && mDatagramController.isPollingInIdleState()) {
                 // Modem can be busy receiving datagrams, so send datagram only when modem is
                 // not busy.
                 mSendingDatagramInProgress = true;
                 datagramArgs.setDatagramStartTime();
-                mDatagramController.updateSendStatus(subId,
+                mDatagramController.updateSendStatus(subId, datagramType,
                         SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
                         getPendingDatagramCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
                 sendRequestAsync(CMD_SEND_SATELLITE_DATAGRAM, datagramArgs, phone);
             } else {
-                logd("sendDatagram: mSendingDatagramInProgress="
+                plogd("sendDatagram: mSendingDatagramInProgress="
                         + mSendingDatagramInProgress + ", isPollingInIdleState="
                         + mDatagramController.isPollingInIdleState());
             }
@@ -392,21 +429,24 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     protected void setDemoMode(boolean isDemoMode) {
         mIsDemoMode = isDemoMode;
+        plogd("setDemoMode: mIsDemoMode=" + mIsDemoMode);
     }
 
+    /**
+     * Set whether the device is aligned with the satellite.
+     */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    protected void setDeviceAlignedWithSatellite(boolean isAligned) {
-        if (mIsDemoMode) {
-            synchronized (mLock) {
-                mIsAligned = isAligned;
-                if (isAligned) handleEventSatelliteAligned();
-            }
+    public void setDeviceAlignedWithSatellite(boolean isAligned) {
+        synchronized (mLock) {
+            mIsAligned = isAligned;
+            plogd("setDeviceAlignedWithSatellite: " + mIsAligned);
+            if (isAligned && mIsDemoMode) handleEventSatelliteAligned();
         }
     }
 
     private void startSatelliteAlignedTimer(@NonNull DatagramDispatcherHandlerRequest request) {
         if (isSatelliteAlignedTimerStarted()) {
-            logd("Satellite aligned timer was already started");
+            plogd("Satellite aligned timer was already started");
             return;
         }
         mSendSatelliteDatagramRequest = request;
@@ -425,7 +465,7 @@
             stopSatelliteAlignedTimer();
 
             if (mSendSatelliteDatagramRequest == null) {
-                loge("handleEventSatelliteAligned: mSendSatelliteDatagramRequest is null");
+                ploge("handleEventSatelliteAligned: mSendSatelliteDatagramRequest is null");
             } else {
                 SendSatelliteDatagramArgument argument =
                         (SendSatelliteDatagramArgument) mSendSatelliteDatagramRequest.argument;
@@ -435,15 +475,18 @@
                 mSendSatelliteDatagramRequest = null;
                 AsyncResult.forMessage(message, null, null);
                 message.sendToTarget();
+                plogd("handleEventSatelliteAligned: EVENT_SEND_SATELLITE_DATAGRAM_DONE");
             }
         }
     }
 
     private void handleEventSatelliteAlignedTimeout(
             @NonNull DatagramDispatcherHandlerRequest request) {
+        plogd("handleEventSatelliteAlignedTimeout");
+        mSendSatelliteDatagramRequest = null;
         SatelliteManager.SatelliteException exception =
                 new SatelliteManager.SatelliteException(
-                        SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+                        SATELLITE_RESULT_NOT_REACHABLE);
         Message message = obtainMessage(EVENT_SEND_SATELLITE_DATAGRAM_DONE, request);
         AsyncResult.forMessage(message, null, exception);
         message.sendToTarget();
@@ -463,15 +506,15 @@
      */
     @GuardedBy("mLock")
     private void sendPendingDatagrams() {
-        logd("sendPendingDatagrams()");
+        plogd("sendPendingDatagrams()");
         if (!mDatagramController.isPollingInIdleState()) {
             // Datagram should be sent to satellite modem when modem is free.
-            logd("sendPendingDatagrams: modem is receiving datagrams");
+            plogd("sendPendingDatagrams: modem is receiving datagrams");
             return;
         }
 
         if (getPendingDatagramCount() <= 0) {
-            logd("sendPendingDatagrams: no pending datagrams to send");
+            plogd("sendPendingDatagrams: no pending datagrams to send");
             return;
         }
 
@@ -484,12 +527,17 @@
         }
 
         if ((pendingDatagram != null) && pendingDatagram.iterator().hasNext()) {
-            mSendingDatagramInProgress = true;
             SendSatelliteDatagramArgument datagramArg =
                     pendingDatagram.iterator().next().getValue();
+            if (mDatagramController.needsWaitingForSatelliteConnected(datagramArg.datagramType)) {
+                plogd("sendPendingDatagrams: wait for satellite connected");
+                return;
+            }
+
+            mSendingDatagramInProgress = true;
             // Sets the trigger time for getting pending datagrams
             datagramArg.setDatagramStartTime();
-            mDatagramController.updateSendStatus(datagramArg.subId,
+            mDatagramController.updateSendStatus(datagramArg.subId, datagramArg.datagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING,
                     getPendingDatagramCount(), SatelliteManager.SATELLITE_RESULT_SUCCESS);
             sendRequestAsync(CMD_SEND_SATELLITE_DATAGRAM, datagramArg, phone);
@@ -509,14 +557,13 @@
         if (pendingDatagramsMap.size() == 0) {
             return;
         }
-        loge("sendErrorCodeAndCleanupPendingDatagrams: cleaning up resources");
+        ploge("sendErrorCodeAndCleanupPendingDatagrams: cleaning up resources");
 
         // Send error code to all the pending datagrams
         for (Entry<Long, SendSatelliteDatagramArgument> entry :
                 pendingDatagramsMap.entrySet()) {
             SendSatelliteDatagramArgument argument = entry.getValue();
             reportSendDatagramCompleted(argument, errorCode);
-            mControllerMetricsStats.reportOutgoingDatagramFailCount(argument.datagramType);
             argument.callback.accept(errorCode);
         }
 
@@ -533,7 +580,7 @@
     @GuardedBy("mLock")
     private void abortSendingPendingDatagrams(int subId,
             @SatelliteManager.SatelliteResult int errorCode) {
-        logd("abortSendingPendingDatagrams()");
+        plogd("abortSendingPendingDatagrams()");
         sendErrorCodeAndCleanupPendingDatagrams(mPendingEmergencyDatagramsMap, errorCode);
         sendErrorCodeAndCleanupPendingDatagrams(mPendingNonEmergencyDatagramsMap, errorCode);
     }
@@ -548,6 +595,22 @@
         }
     }
 
+    /** Return pending user messages count */
+    public int getPendingUserMessagesCount() {
+        synchronized (mLock) {
+            int pendingUserMessagesCount = 0;
+            for (Entry<Long, SendSatelliteDatagramArgument> entry :
+                    mPendingNonEmergencyDatagramsMap.entrySet()) {
+                SendSatelliteDatagramArgument argument = entry.getValue();
+                if (argument.datagramType != SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
+                    pendingUserMessagesCount += 1;
+                }
+            }
+            pendingUserMessagesCount += mPendingEmergencyDatagramsMap.size();
+            return pendingUserMessagesCount;
+        }
+    }
+
     /**
      * Posts the specified command to be executed on the main thread and returns immediately.
      *
@@ -569,9 +632,22 @@
                         .setDatagramType(argument.datagramType)
                         .setResultCode(resultCode)
                         .setDatagramSizeBytes(argument.getDatagramRoundedSizeBytes())
-                        .setDatagramTransferTimeMillis(
-                                System.currentTimeMillis() - argument.datagramStartTime)
+                        /* In case pending datagram has not been attempted to send to modem
+                        interface. transfer time will be 0. */
+                        .setDatagramTransferTimeMillis(argument.datagramStartTime > 0
+                                ? (System.currentTimeMillis() - argument.datagramStartTime) : 0)
+                        .setIsDemoMode(mIsDemoMode)
                         .build());
+        if (resultCode == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
+            mControllerMetricsStats.reportOutgoingDatagramSuccessCount(argument.datagramType,
+                    mIsDemoMode);
+            mSessionMetricsStats.addCountOfSuccessfulOutgoingDatagram(argument.datagramType);
+        } else {
+            mControllerMetricsStats.reportOutgoingDatagramFailCount(argument.datagramType,
+                    mIsDemoMode);
+            mSessionMetricsStats.addCountOfFailedOutgoingDatagram(argument.datagramType,
+                    resultCode);
+        }
     }
 
     /**
@@ -592,7 +668,7 @@
         synchronized (mLock) {
             if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF
                     || state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) {
-                logd("onSatelliteModemStateChanged: cleaning up resources");
+                plogd("onSatelliteModemStateChanged: cleaning up resources");
                 cleanUpResources();
             } else if (state == SatelliteManager.SATELLITE_MODEM_STATE_IDLE) {
                 sendPendingDatagrams();
@@ -608,14 +684,16 @@
 
     @GuardedBy("mLock")
     private void cleanUpResources() {
+        plogd("cleanUpResources");
         mSendingDatagramInProgress = false;
         if (getPendingDatagramCount() > 0) {
-            mDatagramController.updateSendStatus(
-                    SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+            mDatagramController.updateSendStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                    mLastSendRequestDatagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
                     getPendingDatagramCount(), SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
         }
         mDatagramController.updateSendStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                mLastSendRequestDatagramType,
                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
                 0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
         abortSendingPendingDatagrams(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
@@ -624,19 +702,23 @@
         stopSatelliteAlignedTimer();
         stopDatagramWaitForConnectedStateTimer();
         stopWaitForDatagramSendingResponseTimer();
+        stopWaitForSimulatedPollDatagramsDelayTimer();
         mIsDemoMode = false;
         mSendSatelliteDatagramRequest = null;
         mIsAligned = false;
+        mLastSendRequestDatagramType = DATAGRAM_TYPE_UNKNOWN;
     }
 
-    private void startDatagramWaitForConnectedStateTimer() {
+    private void startDatagramWaitForConnectedStateTimer(
+            @NonNull SendSatelliteDatagramArgument datagramArgs) {
         if (isDatagramWaitForConnectedStateTimerStarted()) {
-            logd("DatagramWaitForConnectedStateTimer is already started");
+            plogd("DatagramWaitForConnectedStateTimer is already started");
             return;
         }
         sendMessageDelayed(obtainMessage(
-                        EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT),
-                mDatagramController.getDatagramWaitTimeForConnectedState());
+                        EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT, datagramArgs),
+                mDatagramController.getDatagramWaitTimeForConnectedState(
+                        SatelliteServiceUtils.isLastSosMessage(datagramArgs.datagramType)));
     }
 
     private void stopDatagramWaitForConnectedStateTimer() {
@@ -662,31 +744,38 @@
     private void startWaitForDatagramSendingResponseTimer(
             @NonNull SendSatelliteDatagramArgument argument) {
         if (hasMessages(EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT)) {
-            logd("WaitForDatagramSendingResponseTimer was already started");
+            plogd("WaitForDatagramSendingResponseTimer was already started");
             return;
         }
+        long waitTime = SatelliteServiceUtils.isLastSosMessage(argument.datagramType)
+                ? mWaitTimeForDatagramSendingForLastMessageResponse
+                : mWaitTimeForDatagramSendingResponse;
+        logd("startWaitForDatagramSendingResponseTimer: datagramType=" + argument.datagramType
+                + ", waitTime=" + waitTime);
         sendMessageDelayed(obtainMessage(
-                EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT, argument),
-                mWaitTimeForDatagramSendingResponse);
+                EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT, argument), waitTime);
     }
 
     private void stopWaitForDatagramSendingResponseTimer() {
         removeMessages(EVENT_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMED_OUT);
     }
 
-    private void handleEventDatagramWaitForConnectedStateTimedOut() {
-        logw("Timed out to wait for satellite connected before sending datagrams");
+    private void handleEventDatagramWaitForConnectedStateTimedOut(
+            @NonNull SendSatelliteDatagramArgument argument) {
+        plogw("Timed out to wait for satellite connected before sending datagrams");
         synchronized (mLock) {
             // Update send status
             mDatagramController.updateSendStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                    argument.datagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
                     getPendingDatagramCount(),
-                    SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+                    SATELLITE_RESULT_NOT_REACHABLE);
             mDatagramController.updateSendStatus(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                    argument.datagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
                     0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
             abortSendingPendingDatagrams(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                    SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+                    SATELLITE_RESULT_NOT_REACHABLE);
         }
     }
 
@@ -702,7 +791,7 @@
             return mShouldSendDatagramToModemInDemoMode.get();
 
         } catch (Resources.NotFoundException ex) {
-            loge("shouldSendDatagramToModemInDemoMode: id= "
+            ploge("shouldSendDatagramToModemInDemoMode: id= "
                     + R.bool.config_send_satellite_datagram_to_modem_in_demo_mode + ", ex=" + ex);
             return false;
         }
@@ -713,10 +802,15 @@
                 R.integer.config_wait_for_datagram_sending_response_timeout_millis);
     }
 
+    private long getWaitForDatagramSendingResponseForLastMessageTimeoutMillis() {
+        return mContext.getResources().getInteger(R.integer
+                .config_wait_for_datagram_sending_response_for_last_message_timeout_millis);
+    }
+
     private boolean shouldProcessEventSendSatelliteDatagramDone(
             @NonNull SendSatelliteDatagramArgument argument) {
         synchronized (mLock) {
-            if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+            if (SatelliteServiceUtils.isSosMessage(argument.datagramType)) {
                 return mPendingEmergencyDatagramsMap.containsKey(argument.datagramId);
             } else {
                 return mPendingNonEmergencyDatagramsMap.containsKey(argument.datagramId);
@@ -727,7 +821,7 @@
     private void handleEventWaitForDatagramSendingResponseTimedOut(
             @NonNull SendSatelliteDatagramArgument argument) {
         synchronized (mLock) {
-            logw("Timed out to wait for the response of the request to send the datagram "
+            plogw("Timed out to wait for the response of the request to send the datagram "
                     + argument.datagramId);
 
             // Ask vendor service to abort all datagram-sending requests
@@ -736,10 +830,10 @@
             mSendingDatagramInProgress = false;
 
             // Update send status
-            mDatagramController.updateSendStatus(argument.subId,
+            mDatagramController.updateSendStatus(argument.subId, argument.datagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED,
                     getPendingDatagramCount(), SATELLITE_RESULT_MODEM_TIMEOUT);
-            mDatagramController.updateSendStatus(argument.subId,
+            mDatagramController.updateSendStatus(argument.subId, argument.datagramType,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
                     0, SatelliteManager.SATELLITE_RESULT_SUCCESS);
 
@@ -749,17 +843,15 @@
 
             // Log metrics about the outgoing datagram
             reportSendDatagramCompleted(argument, SATELLITE_RESULT_MODEM_TIMEOUT);
-            mControllerMetricsStats.reportOutgoingDatagramFailCount(argument.datagramType);
             // Remove current datagram from pending map.
-            if (argument.datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
+            if (SatelliteServiceUtils.isSosMessage(argument.datagramType)) {
                 mPendingEmergencyDatagramsMap.remove(argument.datagramId);
             } else {
                 mPendingNonEmergencyDatagramsMap.remove(argument.datagramId);
             }
 
             // Abort sending all the pending datagrams
-            abortSendingPendingDatagrams(argument.subId,
-                    SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
+            abortSendingPendingDatagrams(argument.subId, SATELLITE_RESULT_MODEM_TIMEOUT);
         }
     }
 
@@ -774,7 +866,7 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     protected void setShouldSendDatagramToModemInDemoMode(
             @Nullable Boolean shouldSendToModemInDemoMode) {
-        logd("setShouldSendDatagramToModemInDemoMode(" + (shouldSendToModemInDemoMode == null
+        plogd("setShouldSendDatagramToModemInDemoMode(" + (shouldSendToModemInDemoMode == null
                 ? "null" : shouldSendToModemInDemoMode) + ")");
 
         if (shouldSendToModemInDemoMode == null) {
@@ -789,6 +881,59 @@
         }
     }
 
+    private void startWaitForSimulatedPollDatagramsDelayTimer(
+            @NonNull DatagramDispatcherHandlerRequest request) {
+        if (mIsDemoMode) {
+            plogd("startWaitForSimulatedPollDatagramsDelayTimer");
+            sendMessageDelayed(
+                    obtainMessage(EVENT_WAIT_FOR_SIMULATED_POLL_DATAGRAMS_DELAY_TIMED_OUT, request),
+                    getDemoTimeoutDuration());
+        } else {
+            plogd("Should not start WaitForSimulatedPollDatagramsDelayTimer in non-demo mode");
+        }
+    }
+
+    private void stopWaitForSimulatedPollDatagramsDelayTimer() {
+        removeMessages(EVENT_WAIT_FOR_SIMULATED_POLL_DATAGRAMS_DELAY_TIMED_OUT);
+    }
+
+    private void handleEventWaitForSimulatedPollDatagramsDelayTimedOut(
+            @NonNull SendSatelliteDatagramArgument argument) {
+        if (mIsDemoMode) {
+            plogd("handleEventWaitForSimulatedPollDatagramsDelayTimedOut");
+            mDatagramController.pushDemoModeDatagram(argument.datagramType, argument.datagram);
+            Consumer<Integer> internalCallback = new Consumer<Integer>() {
+                @Override
+                public void accept(Integer result) {
+                    plogd("pollPendingSatelliteDatagrams result: " + result);
+                }
+            };
+            mDatagramController.pollPendingSatelliteDatagrams(argument.subId, internalCallback);
+        } else {
+            plogd("Unexpected EVENT_WAIT_FOR_SIMULATED_POLL_DATAGRAMS_DELAY_TIMED_OUT in "
+                    + "non-demo mode");
+        }
+    }
+
+    long getDemoTimeoutDuration() {
+        return mDemoTimeoutDuration;
+    }
+
+    /**
+     * This API is used by CTS tests to override the mDemoTimeoutDuration.
+     */
+    void setTimeoutDatagramDelayInDemoMode(boolean reset, long timeoutMillis) {
+        if (!mIsDemoMode) {
+            return;
+        }
+        if (reset) {
+            mDemoTimeoutDuration = TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE;
+        } else {
+            mDemoTimeoutDuration = timeoutMillis;
+        }
+        plogd("setTimeoutDatagramDelayInDemoMode " + mDemoTimeoutDuration + " reset=" + reset);
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
@@ -798,4 +943,38 @@
     }
 
     private static void logw(@NonNull String log) { Rlog.w(TAG, log); }
+
+    private boolean isSatellitePersistentLoggingEnabled(
+            @NonNull Context context, @NonNull FeatureFlags featureFlags) {
+        if (featureFlags.satellitePersistentLogging()) {
+            return true;
+        }
+        try {
+            return context.getResources().getBoolean(
+                    R.bool.config_dropboxmanager_persistent_logging_enabled);
+        } catch (RuntimeException e) {
+            return false;
+        }
+    }
+
+    private void plogd(@NonNull String log) {
+        Rlog.d(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.debug(TAG, log);
+        }
+    }
+
+    private void plogw(@NonNull String log) {
+        Rlog.w(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.warn(TAG, log);
+        }
+    }
+
+    private void ploge(@NonNull String log) {
+        Rlog.e(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.error(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 c267fd7..ea75f03 100644
--- a/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
+++ b/src/java/com/android/internal/telephony/satellite/DatagramReceiver.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.satellite;
 
 import static android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
 import static com.android.internal.telephony.satellite.DatagramController.ROUNDING_UNIT;
 
@@ -36,6 +37,8 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.provider.Telephony;
+import android.telephony.DropBoxManagerLoggerBackend;
+import android.telephony.PersistentLogger;
 import android.telephony.Rlog;
 import android.telephony.SubscriptionManager;
 import android.telephony.satellite.ISatelliteDatagramCallback;
@@ -49,8 +52,10 @@
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.IVoidConsumer;
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.metrics.SatelliteStats;
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
+import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
 import com.android.internal.util.FunctionalUtils;
 
 import java.util.concurrent.ConcurrentHashMap;
@@ -79,7 +84,9 @@
     @NonNull private SharedPreferences mSharedPreferences = null;
     @NonNull private final DatagramController mDatagramController;
     @NonNull private final ControllerMetricsStats mControllerMetricsStats;
+    @NonNull private final SessionMetricsStats mSessionMetricsStats;
     @NonNull private final Looper mLooper;
+    @NonNull private final FeatureFlags mFeatureFlags;
 
     private long mDatagramTransferStartTime = 0;
     private boolean mIsDemoMode = false;
@@ -90,6 +97,8 @@
     @Nullable
     private DatagramReceiverHandlerRequest mPendingPollSatelliteDatagramsRequest = null;
     private final Object mLock = new Object();
+    @Nullable
+    private PersistentLogger mPersistentLogger = null;
 
     /**
      * Map key: subId, value: SatelliteDatagramListenerHandler to notify registrants.
@@ -109,13 +118,15 @@
      * Create the DatagramReceiver singleton instance.
      * @param context The Context to use to create the DatagramReceiver.
      * @param looper The looper for the handler.
+     * @param featureFlags The telephony feature flags.
      * @param datagramController DatagramController which is used to update datagram transfer state.
      * @return The singleton instance of DatagramReceiver.
      */
     public static DatagramReceiver make(@NonNull Context context, @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags,
             @NonNull DatagramController datagramController) {
         if (sInstance == null) {
-            sInstance = new DatagramReceiver(context, looper, datagramController);
+            sInstance = new DatagramReceiver(context, looper, featureFlags, datagramController);
         }
         return sInstance;
     }
@@ -126,24 +137,31 @@
      *
      * @param context The Context for the DatagramReceiver.
      * @param looper The looper for the handler.
+     * @param featureFlags The telephony feature flags.
      * @param datagramController DatagramController which is used to update datagram transfer state.
      */
     @VisibleForTesting
     protected DatagramReceiver(@NonNull Context context, @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags,
             @NonNull DatagramController datagramController) {
         super(looper);
         mContext = context;
         mLooper = looper;
+        mFeatureFlags = featureFlags;
         mContentResolver = context.getContentResolver();
         mDatagramController = datagramController;
         mControllerMetricsStats = ControllerMetricsStats.getInstance();
-
+        mSessionMetricsStats = SessionMetricsStats.getInstance();
+        if (isSatellitePersistentLoggingEnabled(context, featureFlags)) {
+            mPersistentLogger = new PersistentLogger(
+                    DropBoxManagerLoggerBackend.getInstance(context));
+        }
         try {
             mSharedPreferences =
                     mContext.getSharedPreferences(SatelliteController.SATELLITE_SHARED_PREF,
                             Context.MODE_PRIVATE);
         } catch (Exception e) {
-            loge("Cannot get default shared preferences: " + e);
+            ploge("Cannot get default shared preferences: " + e);
         }
     }
 
@@ -354,9 +372,6 @@
                                     obtainMessage(EVENT_RETRY_DELIVERING_RECEIVED_DATAGRAM,
                                             argument), getTimeoutToReceiveAck());
                         });
-
-                        sInstance.mControllerMetricsStats.reportIncomingDatagramCount(
-                                SatelliteManager.SATELLITE_RESULT_SUCCESS);
                     }
 
                     if (pendingCount <= 0) {
@@ -377,8 +392,8 @@
                     }
 
                     // Send the captured data about incoming datagram to metric
-                    sInstance.reportMetrics(
-                            satelliteDatagram, SatelliteManager.SATELLITE_RESULT_SUCCESS);
+                    sInstance.reportMetrics(satelliteDatagram,
+                            SatelliteManager.SATELLITE_RESULT_SUCCESS);
                     break;
                 }
 
@@ -439,7 +454,7 @@
                         "pollPendingSatelliteDatagrams");
 
                 if (mIsDemoMode && error == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
-                    SatelliteDatagram datagram = mDatagramController.getDemoModeDatagram();
+                    SatelliteDatagram datagram = mDatagramController.popDemoModeDatagram();
                     final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(
                             request.subId, mContext);
                     SatelliteDatagramListenerHandler listenerHandler =
@@ -456,7 +471,7 @@
                     }
                 }
 
-                logd("EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE error: " + error);
+                plogd("EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE error: " + error);
                 if (error != SatelliteManager.SATELLITE_RESULT_SUCCESS) {
                     mDatagramController.updateReceiveStatus(request.subId,
                             SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
@@ -468,7 +483,6 @@
                             SatelliteManager.SATELLITE_RESULT_SUCCESS);
 
                     reportMetrics(null, error);
-                    mControllerMetricsStats.reportIncomingDatagramCount(error);
                 }
                 // Send response for current request
                 ((Consumer<Integer>) request.argument).accept(error);
@@ -485,7 +499,7 @@
                 break;
 
             default:
-                logw("DatagramDispatcherHandler: unexpected message code: " + msg.what);
+                plogw("DatagramDispatcherHandler: unexpected message code: " + msg.what);
                 break;
         }
     }
@@ -558,7 +572,7 @@
     public void pollPendingSatelliteDatagrams(int subId, @NonNull Consumer<Integer> callback) {
         if (!mDatagramController.isPollingInIdleState()) {
             // Poll request should be sent to satellite modem only when it is free.
-            logd("pollPendingSatelliteDatagrams: satellite modem is busy receiving datagrams.");
+            plogd("pollPendingSatelliteDatagrams: satellite modem is busy receiving datagrams.");
             callback.accept(SatelliteManager.SATELLITE_RESULT_MODEM_BUSY);
             return;
         }
@@ -570,7 +584,7 @@
             if (isDatagramWaitForConnectedStateTimerStarted()) {
                 stopDatagramWaitForConnectedStateTimer();
                 if (mPendingPollSatelliteDatagramsRequest == null) {
-                    loge("handleSatelliteConnectedEvent: mPendingPollSatelliteDatagramsRequest is"
+                    ploge("handleSatelliteConnectedEvent: mPendingPollSatelliteDatagramsRequest is"
                             + " null");
                     return;
                 }
@@ -588,14 +602,15 @@
             @NonNull Consumer<Integer> callback) {
         if (!mDatagramController.isSendingInIdleState()) {
             // Poll request should be sent to satellite modem only when it is free.
-            logd("pollPendingSatelliteDatagramsInternal: satellite modem is busy sending "
+            plogd("pollPendingSatelliteDatagramsInternal: satellite modem is busy sending "
                     + "datagrams.");
             callback.accept(SatelliteManager.SATELLITE_RESULT_MODEM_BUSY);
             return;
         }
 
-        if (mDatagramController.needsWaitingForSatelliteConnected()) {
-            logd("pollPendingSatelliteDatagramsInternal: wait for satellite connected");
+        if (mDatagramController.needsWaitingForSatelliteConnected(
+                SatelliteManager.DATAGRAM_TYPE_UNKNOWN)) {
+            plogd("pollPendingSatelliteDatagramsInternal: wait for satellite connected");
             synchronized (mLock) {
                 mPendingPollSatelliteDatagramsRequest = new DatagramReceiverHandlerRequest(
                         callback, SatelliteServiceUtils.getPhone(), subId);
@@ -619,7 +634,7 @@
             DatagramReceiverHandlerRequest request = new DatagramReceiverHandlerRequest(
                     callback, phone, subId);
             synchronized (mLock) {
-                if (mIsAligned) {
+                if (!mDatagramController.waitForAligningToSatellite(mIsAligned)) {
                     Message msg = obtainMessage(EVENT_POLL_PENDING_SATELLITE_DATAGRAMS_DONE,
                             request);
                     AsyncResult.forMessage(msg, null, null);
@@ -643,7 +658,7 @@
         synchronized (mLock) {
             if (state == SatelliteManager.SATELLITE_MODEM_STATE_OFF
                     || state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE) {
-                logd("onSatelliteModemStateChanged: cleaning up resources");
+                plogd("onSatelliteModemStateChanged: cleaning up resources");
                 cleanUpResources();
             } else if (state == SATELLITE_MODEM_STATE_CONNECTED) {
                 handleSatelliteConnectedEvent();
@@ -656,7 +671,7 @@
         if (isSatelliteAlignedTimerStarted()) {
             stopSatelliteAlignedTimer();
             if (mDemoPollPendingSatelliteDatagramsRequest == null) {
-                loge("Satellite aligned timer was started "
+                ploge("Satellite aligned timer was started "
                         + "but mDemoPollPendingSatelliteDatagramsRequest is null");
             } else {
                 Consumer<Integer> callback =
@@ -712,7 +727,7 @@
     private void reportMetrics(@Nullable SatelliteDatagram satelliteDatagram,
             @NonNull @SatelliteManager.SatelliteResult int resultCode) {
         int datagramSizeRoundedBytes = -1;
-        int datagramTransferTime = 0;
+        long datagramTransferTime = 0;
 
         if (satelliteDatagram != null) {
             if (satelliteDatagram.getSatelliteDatagram() != null) {
@@ -721,7 +736,7 @@
                 datagramSizeRoundedBytes =
                         (int) (Math.round((double) sizeBytes / ROUNDING_UNIT) * ROUNDING_UNIT);
             }
-            datagramTransferTime = (int) (System.currentTimeMillis() - mDatagramTransferStartTime);
+            datagramTransferTime = (System.currentTimeMillis() - mDatagramTransferStartTime);
             mDatagramTransferStartTime = 0;
         }
 
@@ -730,7 +745,15 @@
                         .setResultCode(resultCode)
                         .setDatagramSizeBytes(datagramSizeRoundedBytes)
                         .setDatagramTransferTimeMillis(datagramTransferTime)
+                        .setIsDemoMode(mIsDemoMode)
                         .build());
+
+        mControllerMetricsStats.reportIncomingDatagramCount(resultCode, mIsDemoMode);
+        if (resultCode == SATELLITE_RESULT_SUCCESS) {
+            mSessionMetricsStats.addCountOfSuccessfulIncomingDatagram();
+        } else {
+            mSessionMetricsStats.addCountOfFailedIncomingDatagram();
+        }
     }
 
     /** Set demo mode
@@ -743,18 +766,17 @@
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-    protected void setDeviceAlignedWithSatellite(boolean isAligned) {
-        if (mIsDemoMode) {
-            synchronized (mLock) {
-                mIsAligned = isAligned;
-                if (isAligned) handleEventSatelliteAligned();
-            }
+    public void setDeviceAlignedWithSatellite(boolean isAligned) {
+        synchronized (mLock) {
+            mIsAligned = isAligned;
+            plogd("setDeviceAlignedWithSatellite: " + mIsAligned);
+            if (isAligned && mIsDemoMode) handleEventSatelliteAligned();
         }
     }
 
     private void startSatelliteAlignedTimer(DatagramReceiverHandlerRequest request) {
         if (isSatelliteAlignedTimerStarted()) {
-            logd("Satellite aligned timer was already started");
+            plogd("Satellite aligned timer was already started");
             return;
         }
         mDemoPollPendingSatelliteDatagramsRequest = request;
@@ -773,7 +795,7 @@
             stopSatelliteAlignedTimer();
 
             if (mDemoPollPendingSatelliteDatagramsRequest == null) {
-                loge("handleSatelliteAlignedTimer: mDemoPollPendingSatelliteDatagramsRequest "
+                ploge("handleSatelliteAlignedTimer: mDemoPollPendingSatelliteDatagramsRequest "
                         + "is null");
             } else {
                 Message message = obtainMessage(
@@ -805,12 +827,12 @@
 
     private void startDatagramWaitForConnectedStateTimer() {
         if (isDatagramWaitForConnectedStateTimerStarted()) {
-            logd("DatagramWaitForConnectedStateTimer is already started");
+            plogd("DatagramWaitForConnectedStateTimer is already started");
             return;
         }
         sendMessageDelayed(obtainMessage(
                         EVENT_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMED_OUT),
-                mDatagramController.getDatagramWaitTimeForConnectedState());
+                mDatagramController.getDatagramWaitTimeForConnectedState(false));
     }
 
     private void stopDatagramWaitForConnectedStateTimer() {
@@ -825,12 +847,12 @@
     private void handleEventDatagramWaitForConnectedStateTimedOut() {
         synchronized (mLock) {
             if (mPendingPollSatelliteDatagramsRequest == null) {
-                logw("handleEventDatagramWaitForConnectedStateTimedOut: "
+                plogw("handleEventDatagramWaitForConnectedStateTimedOut: "
                         + "mPendingPollSatelliteDatagramsRequest is null");
                 return;
             }
 
-            logw("Timed out to wait for satellite connected before polling datagrams");
+            plogw("Timed out to wait for satellite connected before polling datagrams");
             mDatagramController.updateReceiveStatus(mPendingPollSatelliteDatagramsRequest.subId,
                     SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED,
                     mDatagramController.getReceivePendingCount(),
@@ -842,8 +864,6 @@
                     SatelliteManager.SATELLITE_RESULT_SUCCESS);
 
             reportMetrics(null, SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
-            mControllerMetricsStats.reportIncomingDatagramCount(
-                    SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
 
             Consumer<Integer> callback =
                     (Consumer<Integer>) mPendingPollSatelliteDatagramsRequest.argument;
@@ -871,4 +891,38 @@
     private static void logw(@NonNull String log) {
         Rlog.w(TAG, log);
     }
+
+    private boolean isSatellitePersistentLoggingEnabled(
+            @NonNull Context context, @NonNull FeatureFlags featureFlags) {
+        if (featureFlags.satellitePersistentLogging()) {
+            return true;
+        }
+        try {
+            return context.getResources().getBoolean(
+                    R.bool.config_dropboxmanager_persistent_logging_enabled);
+        } catch (RuntimeException e) {
+            return false;
+        }
+    }
+
+    private void plogd(@NonNull String log) {
+        Rlog.d(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.debug(TAG, log);
+        }
+    }
+
+    private void plogw(@NonNull String log) {
+        Rlog.w(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.warn(TAG, log);
+        }
+    }
+
+    private void ploge(@NonNull String log) {
+        Rlog.e(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.error(TAG, log);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/DemoSimulator.java b/src/java/com/android/internal/telephony/satellite/DemoSimulator.java
new file mode 100644
index 0000000..3c31ae8
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/DemoSimulator.java
@@ -0,0 +1,329 @@
+/*
+ * 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.satellite;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telephony.IIntegerConsumer;
+import android.telephony.satellite.stub.ISatelliteListener;
+import android.telephony.satellite.stub.NtnSignalStrength;
+import android.telephony.satellite.stub.SatelliteModemState;
+import android.telephony.satellite.stub.SatelliteResult;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+public class DemoSimulator extends StateMachine {
+    private static final String TAG = "DemoSimulator";
+    private static final boolean DBG = true;
+
+    private static final int EVENT_SATELLITE_MODE_ON = 1;
+    private static final int EVENT_SATELLITE_MODE_OFF = 2;
+    private static final int EVENT_DEVICE_ALIGNED_WITH_SATELLITE = 3;
+    protected static final int EVENT_DEVICE_ALIGNED = 4;
+    protected static final int EVENT_DEVICE_NOT_ALIGNED = 5;
+
+    @NonNull private static DemoSimulator sInstance;
+
+    @NonNull private final Context mContext;
+    @NonNull private final SatelliteController mSatelliteController;
+    @NonNull private final PowerOffState mPowerOffState = new PowerOffState();
+    @NonNull private final NotConnectedState mNotConnectedState = new NotConnectedState();
+    @NonNull private final ConnectedState mConnectedState = new ConnectedState();
+    @NonNull private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private boolean mIsAligned = false;
+    private ISatelliteListener mISatelliteListener;
+
+    /**
+     * @return The singleton instance of DemoSimulator.
+     */
+    public static DemoSimulator getInstance() {
+        if (sInstance == null) {
+            Log.e(TAG, "DemoSimulator was not yet initialized.");
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create the DemoSimulator singleton instance.
+     *
+     * @param context The Context for the DemoSimulator.
+     * @return The singleton instance of DemoSimulator.
+     */
+    public static DemoSimulator make(@NonNull Context context,
+            @NonNull SatelliteController satelliteController) {
+        if (sInstance == null) {
+            sInstance = new DemoSimulator(context, Looper.getMainLooper(), satelliteController);
+        }
+        return sInstance;
+    }
+
+    /**
+     * Create a DemoSimulator.
+     *
+     * @param context The Context for the DemoSimulator.
+     * @param looper The looper associated with the handler of this class.
+     */
+    protected DemoSimulator(@NonNull Context context, @NonNull Looper looper,
+            @NonNull SatelliteController satelliteController) {
+        super(TAG, looper);
+
+        mContext = context;
+        mSatelliteController = satelliteController;
+        addState(mPowerOffState);
+        addState(mNotConnectedState);
+        addState(mConnectedState);
+        setInitialState(mPowerOffState);
+        start();
+    }
+
+    private class PowerOffState extends State {
+        @Override
+        public void enter() {
+            logd("Entering PowerOffState");
+        }
+
+        @Override
+        public void exit() {
+            logd("Exiting PowerOffState");
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("PowerOffState: processing " + getWhatToString(msg.what));
+            switch (msg.what) {
+                case EVENT_SATELLITE_MODE_ON:
+                    transitionTo(mNotConnectedState);
+                    break;
+            }
+            // Ignore all unexpected events.
+            return HANDLED;
+        }
+    }
+
+    private class NotConnectedState extends State {
+        @Override
+        public void enter() {
+            logd("Entering NotConnectedState");
+
+            try {
+                NtnSignalStrength ntnSignalStrength = new NtnSignalStrength();
+                ntnSignalStrength.signalStrengthLevel = 0;
+                mISatelliteListener.onSatelliteModemStateChanged(
+                        SatelliteModemState.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+                mISatelliteListener.onNtnSignalStrengthChanged(ntnSignalStrength);
+
+                synchronized (mLock) {
+                    if (mIsAligned) {
+                        handleEventDeviceAlignedWithSatellite(true);
+                    }
+                }
+            } catch (RemoteException e) {
+                loge("NotConnectedState: RemoteException " + e);
+            }
+        }
+
+        @Override
+        public void exit() {
+            logd("Exiting NotConnectedState");
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("NotConnectedState: processing " + getWhatToString(msg.what));
+            switch (msg.what) {
+                case EVENT_SATELLITE_MODE_OFF:
+                    transitionTo(mPowerOffState);
+                    break;
+                case EVENT_DEVICE_ALIGNED_WITH_SATELLITE:
+                    handleEventDeviceAlignedWithSatellite((boolean) msg.obj);
+                    break;
+                case EVENT_DEVICE_ALIGNED:
+                    transitionTo(mConnectedState);
+                    break;
+            }
+            // Ignore all unexpected events.
+            return HANDLED;
+        }
+
+        private void handleEventDeviceAlignedWithSatellite(boolean isAligned) {
+            if (isAligned && !hasMessages(EVENT_DEVICE_ALIGNED)) {
+                long durationMillis = mSatelliteController.getDemoPointingAlignedDurationMillis();
+                logd("NotConnectedState: handleEventAlignedWithSatellite isAligned=true."
+                        + " Send delayed EVENT_DEVICE_ALIGNED message in"
+                        + " durationMillis=" + durationMillis);
+                sendMessageDelayed(EVENT_DEVICE_ALIGNED, durationMillis);
+            } else if (!isAligned && hasMessages(EVENT_DEVICE_ALIGNED)) {
+                logd("NotConnectedState: handleEventAlignedWithSatellite isAligned=false."
+                        + " Remove EVENT_DEVICE_ALIGNED message.");
+                removeMessages(EVENT_DEVICE_ALIGNED);
+            }
+        }
+    }
+
+    private class ConnectedState extends State {
+        @Override
+        public void enter() {
+            logd("Entering ConnectedState");
+
+            try {
+                NtnSignalStrength ntnSignalStrength = new NtnSignalStrength();
+                ntnSignalStrength.signalStrengthLevel = 2;
+                mISatelliteListener.onSatelliteModemStateChanged(
+                        SatelliteModemState.SATELLITE_MODEM_STATE_CONNECTED);
+                mISatelliteListener.onNtnSignalStrengthChanged(ntnSignalStrength);
+
+                synchronized (mLock) {
+                    if (!mIsAligned) {
+                        handleEventDeviceAlignedWithSatellite(false);
+                    }
+                }
+            } catch (RemoteException e) {
+                loge("ConnectedState: RemoteException " + e);
+            }
+        }
+
+        @Override
+        public void exit() {
+            logd("Exiting ConnectedState");
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) log("ConnectedState: processing " + getWhatToString(msg.what));
+            switch (msg.what) {
+                case EVENT_SATELLITE_MODE_OFF:
+                    transitionTo(mPowerOffState);
+                    break;
+                case EVENT_DEVICE_ALIGNED_WITH_SATELLITE:
+                    handleEventDeviceAlignedWithSatellite((boolean) msg.obj);
+                    break;
+                case EVENT_DEVICE_NOT_ALIGNED:
+                    transitionTo(mNotConnectedState);
+                    break;
+            }
+            // Ignore all unexpected events.
+            return HANDLED;
+        }
+
+        private void handleEventDeviceAlignedWithSatellite(boolean isAligned) {
+            if (!isAligned && !hasMessages(EVENT_DEVICE_NOT_ALIGNED)) {
+                long durationMillis =
+                        mSatelliteController.getDemoPointingNotAlignedDurationMillis();
+                logd("ConnectedState: handleEventAlignedWithSatellite isAligned=false."
+                        + " Send delayed EVENT_DEVICE_NOT_ALIGNED message"
+                        + " in durationMillis=" + durationMillis);
+                sendMessageDelayed(EVENT_DEVICE_NOT_ALIGNED, durationMillis);
+            } else if (isAligned && hasMessages(EVENT_DEVICE_NOT_ALIGNED)) {
+                logd("ConnectedState: handleEventAlignedWithSatellite isAligned=true."
+                        + " Remove EVENT_DEVICE_NOT_ALIGNED message.");
+                removeMessages(EVENT_DEVICE_NOT_ALIGNED);
+            }
+        }
+    }
+
+    /**
+     * @return the string for msg.what
+     */
+    @Override
+    protected String getWhatToString(int what) {
+        String whatString;
+        switch (what) {
+            case EVENT_SATELLITE_MODE_ON:
+                whatString = "EVENT_SATELLITE_MODE_ON";
+                break;
+            case EVENT_SATELLITE_MODE_OFF:
+                whatString = "EVENT_SATELLITE_MODE_OFF";
+                break;
+            case EVENT_DEVICE_ALIGNED_WITH_SATELLITE:
+                whatString = "EVENT_DEVICE_ALIGNED_WITH_SATELLITE";
+                break;
+            case EVENT_DEVICE_ALIGNED:
+                whatString = "EVENT_DEVICE_ALIGNED";
+                break;
+            case EVENT_DEVICE_NOT_ALIGNED:
+                whatString = "EVENT_DEVICE_NOT_ALIGNED";
+                break;
+            default:
+                whatString = "UNKNOWN EVENT " + what;
+        }
+        return whatString;
+    }
+
+    /**
+     * Register the callback interface with satellite service.
+     *
+     * @param listener The callback interface to handle satellite service indications.
+     */
+    public void setSatelliteListener(@NonNull ISatelliteListener listener) {
+        mISatelliteListener = listener;
+    }
+
+    /**
+     * Allow cellular modem scanning while satellite mode is on.
+     *
+     * @param enabled  {@code true} to enable cellular modem while satellite mode is on
+     *                             and {@code false} to disable
+     * @param errorCallback The callback to receive the error code result of the operation.
+     */
+    public void enableCellularModemWhileSatelliteModeIsOn(boolean enabled,
+            @NonNull IIntegerConsumer errorCallback) {
+        try {
+            errorCallback.accept(SatelliteResult.SATELLITE_RESULT_SUCCESS);
+        } catch (RemoteException e) {
+            loge("enableCellularModemWhileSatelliteModeIsOn: RemoteException " + e);
+        }
+    }
+
+    /**
+     * This function is used by {@link SatelliteSessionController} to notify {@link DemoSimulator}
+     * that satellite mode is ON.
+     */
+    public void onSatelliteModeOn() {
+        if (mSatelliteController.isDemoModeEnabled()) {
+            sendMessage(EVENT_SATELLITE_MODE_ON);
+        }
+    }
+
+    /**
+     * This function is used by {@link SatelliteSessionController} to notify {@link DemoSimulator}
+     * that satellite mode is OFF.
+     */
+    public void onSatelliteModeOff() {
+        sendMessage(EVENT_SATELLITE_MODE_OFF);
+    }
+
+    /**
+     * Set whether the device is aligned with the satellite.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void setDeviceAlignedWithSatellite(boolean isAligned) {
+        synchronized (mLock) {
+            if (mSatelliteController.isDemoModeEnabled()) {
+                mIsAligned = isAligned;
+                sendMessage(EVENT_DEVICE_ALIGNED_WITH_SATELLITE, isAligned);
+            }
+        }
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java b/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java
index add01c0..dfc7919 100644
--- a/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java
+++ b/src/java/com/android/internal/telephony/satellite/NtnCapabilityResolver.java
@@ -39,11 +39,16 @@
      */
     public static void resolveNtnCapability(
             @NonNull NetworkRegistrationInfo networkRegistrationInfo, int subId) {
+        String registeredPlmn = networkRegistrationInfo.getRegisteredPlmn();
+        if (TextUtils.isEmpty(registeredPlmn)) {
+            return;
+        }
+
         SatelliteController satelliteController = SatelliteController.getInstance();
         List<String> satellitePlmnList = satelliteController.getSatellitePlmnsForCarrier(subId);
-        String registeredPlmn = networkRegistrationInfo.getRegisteredPlmn();
         for (String satellitePlmn : satellitePlmnList) {
-            if (TextUtils.equals(satellitePlmn, registeredPlmn)) {
+            if (TextUtils.equals(satellitePlmn, registeredPlmn)
+                    && networkRegistrationInfo.isInService()) {
                 logd("Registered to satellite PLMN " + satellitePlmn);
                 networkRegistrationInfo.setIsNonTerrestrialNetwork(true);
                 networkRegistrationInfo.setAvailableServices(
diff --git a/src/java/com/android/internal/telephony/satellite/PointingAppController.java b/src/java/com/android/internal/telephony/satellite/PointingAppController.java
index 878ee96..06281c7 100644
--- a/src/java/com/android/internal/telephony/satellite/PointingAppController.java
+++ b/src/java/com/android/internal/telephony/satellite/PointingAppController.java
@@ -34,6 +34,8 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.telephony.DropBoxManagerLoggerBackend;
+import android.telephony.PersistentLogger;
 import android.telephony.Rlog;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.PointingInfo;
@@ -42,6 +44,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -60,8 +63,11 @@
     @NonNull
     private static PointingAppController sInstance;
     @NonNull private final Context mContext;
+    @NonNull private final FeatureFlags mFeatureFlags;
     private boolean mStartedSatelliteTransmissionUpdates;
     private boolean mLastNeedFullScreenPointingUI;
+    private boolean mLastIsDemoMode;
+    private boolean mLastIsEmergency;
     private boolean mListenerForPointingUIRegistered;
     @NonNull private String mPointingUiPackageName = "";
     @NonNull private String mPointingUiClassName = "";
@@ -72,6 +78,7 @@
      */
     private final ConcurrentHashMap<Integer, SatelliteTransmissionUpdateHandler>
             mSatelliteTransmissionUpdateHandlers = new ConcurrentHashMap<>();
+    @Nullable private PersistentLogger mPersistentLogger = null;
 
     /**
      * @return The singleton instance of PointingAppController.
@@ -86,11 +93,13 @@
     /**
      * Create the PointingAppController singleton instance.
      * @param context The Context to use to create the PointingAppController.
+     * @param featureFlags The telephony feature flags.
      * @return The singleton instance of PointingAppController.
      */
-    public static PointingAppController make(@NonNull Context context) {
+    public static PointingAppController make(@NonNull Context context,
+            @NonNull FeatureFlags featureFlags) {
         if (sInstance == null) {
-            sInstance = new PointingAppController(context);
+            sInstance = new PointingAppController(context, featureFlags);
         }
         return sInstance;
     }
@@ -101,12 +110,20 @@
      * @param context The Context for the PointingUIController.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public PointingAppController(@NonNull Context context) {
+    public PointingAppController(@NonNull Context context,
+            @NonNull FeatureFlags featureFlags) {
         mContext = context;
+        mFeatureFlags = featureFlags;
         mStartedSatelliteTransmissionUpdates = false;
         mLastNeedFullScreenPointingUI = false;
+        mLastIsDemoMode = false;
+        mLastIsEmergency = false;
         mListenerForPointingUIRegistered = false;
         mActivityManager = mContext.getSystemService(ActivityManager.class);
+        if (isSatellitePersistentLoggingEnabled(context, featureFlags)) {
+            mPersistentLogger = new PersistentLogger(
+                    DropBoxManagerLoggerBackend.getInstance(context));
+        }
     }
 
     /**
@@ -130,15 +147,6 @@
     }
 
     /**
-     * Get the flag mStartedSatelliteTransmissionUpdates
-     * @return returns mStartedSatelliteTransmissionUpdates
-     */
-    @VisibleForTesting
-    public boolean getLastNeedFullScreenPointingUI() {
-        return mLastNeedFullScreenPointingUI;
-    }
-
-    /**
      * Listener for handling pointing UI App in the event of crash
      */
     @VisibleForTesting
@@ -152,20 +160,23 @@
 
             if (callerPackages != null) {
                 if (Arrays.stream(callerPackages).anyMatch(pointingUiPackage::contains)) {
-                    logd("Restarting pointingUI");
-                    startPointingUI(mLastNeedFullScreenPointingUI);
+                    plogd("Restarting pointingUI");
+                    startPointingUI(mLastNeedFullScreenPointingUI, mLastIsDemoMode,
+                            mLastIsEmergency);
                 }
             }
         }
     }
 
     private static final class DatagramTransferStateHandlerRequest {
+        public int datagramType;
         public int datagramTransferState;
         public int pendingCount;
         public int errorCode;
 
-        DatagramTransferStateHandlerRequest(int datagramTransferState, int pendingCount,
-                int errorCode) {
+        DatagramTransferStateHandlerRequest(int datagramType, int datagramTransferState,
+                int pendingCount, int errorCode) {
+            this.datagramType = datagramType;
             this.datagramTransferState = datagramTransferState;
             this.pendingCount = pendingCount;
             this.errorCode = errorCode;
@@ -232,8 +243,9 @@
                     List<IBinder> toBeRemoved = new ArrayList<>();
                     mListeners.values().forEach(listener -> {
                         try {
-                            listener.onSendDatagramStateChanged(request.datagramTransferState,
-                                    request.pendingCount, request.errorCode);
+                            listener.onSendDatagramStateChanged(request.datagramType,
+                                    request.datagramTransferState, request.pendingCount,
+                                    request.errorCode);
                         } catch (RemoteException e) {
                             logd("EVENT_SEND_DATAGRAM_STATE_CHANGED RemoteException: " + e);
                             toBeRemoved.add(listener.asBinder());
@@ -332,7 +344,7 @@
      */
     public void startSatelliteTransmissionUpdates(@NonNull Message message) {
         if (mStartedSatelliteTransmissionUpdates) {
-            logd("startSatelliteTransmissionUpdates: already started");
+            plogd("startSatelliteTransmissionUpdates: already started");
             AsyncResult.forMessage(message, null, new SatelliteManager.SatelliteException(
                     SatelliteManager.SATELLITE_RESULT_SUCCESS));
             message.sendToTarget();
@@ -356,10 +368,11 @@
      * Check if Pointing is needed and Launch Pointing UI
      * @param needFullScreenPointingUI if pointing UI has to be launchd with Full screen
      */
-    public void startPointingUI(boolean needFullScreenPointingUI) {
+    public void startPointingUI(boolean needFullScreenPointingUI, boolean isDemoMode,
+            boolean isEmergency) {
         String packageName = getPointingUiPackageName();
         if (TextUtils.isEmpty(packageName)) {
-            logd("startPointingUI: config_pointing_ui_package is not set. Ignore the request");
+            plogd("startPointingUI: config_pointing_ui_package is not set. Ignore the request");
             return;
         }
 
@@ -373,11 +386,14 @@
             launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(packageName);
         }
         if (launchIntent == null) {
-            loge("startPointingUI: launchIntent is null");
+            ploge("startPointingUI: launchIntent is null");
             return;
         }
-        logd("startPointingUI: needFullScreenPointingUI: " + needFullScreenPointingUI);
+        plogd("startPointingUI: needFullScreenPointingUI: " + needFullScreenPointingUI
+                + ", isDemoMode: " + isDemoMode + ", isEmergency: " + isEmergency);
         launchIntent.putExtra("needFullScreen", needFullScreenPointingUI);
+        launchIntent.putExtra("isDemoMode", isDemoMode);
+        launchIntent.putExtra("isEmergency", isEmergency);
 
         try {
             if (!mListenerForPointingUIRegistered) {
@@ -386,9 +402,11 @@
                 mListenerForPointingUIRegistered = true;
             }
             mLastNeedFullScreenPointingUI = needFullScreenPointingUI;
+            mLastIsDemoMode = isDemoMode;
+            mLastIsEmergency = isEmergency;
             mContext.startActivity(launchIntent);
         } catch (ActivityNotFoundException ex) {
-            loge("startPointingUI: Pointing UI app activity is not found, ex=" + ex);
+            ploge("startPointingUI: Pointing UI app activity is not found, ex=" + ex);
         }
     }
 
@@ -403,10 +421,11 @@
     }
 
     public void updateSendDatagramTransferState(int subId,
+            @SatelliteManager.DatagramType int datagramType,
             @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState,
             int sendPendingCount, int errorCode) {
         DatagramTransferStateHandlerRequest request = new DatagramTransferStateHandlerRequest(
-                datagramTransferState, sendPendingCount, errorCode);
+                datagramType, datagramTransferState, sendPendingCount, errorCode);
         SatelliteTransmissionUpdateHandler handler =
                 mSatelliteTransmissionUpdateHandlers.get(subId);
 
@@ -416,7 +435,7 @@
                     request);
             msg.sendToTarget();
         } else {
-            loge("SatelliteTransmissionUpdateHandler not found for subId: " + subId);
+            ploge("SatelliteTransmissionUpdateHandler not found for subId: " + subId);
         }
     }
 
@@ -424,7 +443,8 @@
             @SatelliteManager.SatelliteDatagramTransferState int datagramTransferState,
             int receivePendingCount, int errorCode) {
         DatagramTransferStateHandlerRequest request = new DatagramTransferStateHandlerRequest(
-                datagramTransferState, receivePendingCount, errorCode);
+                SatelliteManager.DATAGRAM_TYPE_UNKNOWN, datagramTransferState, receivePendingCount,
+                errorCode);
         SatelliteTransmissionUpdateHandler handler =
                 mSatelliteTransmissionUpdateHandlers.get(subId);
 
@@ -434,7 +454,7 @@
                     request);
             msg.sendToTarget();
         } else {
-            loge(" SatelliteTransmissionUpdateHandler not found for subId: " + subId);
+            ploge(" SatelliteTransmissionUpdateHandler not found for subId: " + subId);
         }
     }
 
@@ -449,12 +469,12 @@
     boolean setSatellitePointingUiClassName(
             @Nullable String packageName, @Nullable String className) {
         if (!isMockModemAllowed()) {
-            loge("setSatellitePointingUiClassName: modifying satellite pointing UI package and "
+            ploge("setSatellitePointingUiClassName: modifying satellite pointing UI package and "
                     + "class name is not allowed");
             return false;
         }
 
-        logd("setSatellitePointingUiClassName: config_pointing_ui_package is updated, new "
+        plogd("setSatellitePointingUiClassName: config_pointing_ui_package is updated, new "
                 + "packageName=" + packageName
                 + ", config_pointing_ui_class new className=" + className);
 
@@ -500,6 +520,33 @@
     private static void loge(@NonNull String log) {
         Rlog.e(TAG, log);
     }
+
+    private boolean isSatellitePersistentLoggingEnabled(
+            @NonNull Context context, @NonNull FeatureFlags featureFlags) {
+        if (featureFlags.satellitePersistentLogging()) {
+            return true;
+        }
+        try {
+            return context.getResources().getBoolean(
+                    R.bool.config_dropboxmanager_persistent_logging_enabled);
+        } catch (RuntimeException e) {
+            return false;
+        }
+    }
+
+    private void plogd(@NonNull String log) {
+        Rlog.d(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.debug(TAG, log);
+        }
+    }
+
+    private void ploge(@NonNull String log) {
+        Rlog.e(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.error(TAG, log);
+        }
+    }
     /**
      * TODO: The following needs to be added in this class:
      * - check if pointingUI crashes - then restart it
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteConfig.java b/src/java/com/android/internal/telephony/satellite/SatelliteConfig.java
index 8d7e723..0568d86 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteConfig.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteConfig.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -51,7 +52,7 @@
     private Map<Integer, Map<String, Set<Integer>>> mSupportedServicesPerCarrier;
     private List<String> mSatelliteRegionCountryCodes;
     private Boolean mIsSatelliteRegionAllowed;
-    private Path mSatS2FilePath;
+    private File mSatS2File;
     private SatelliteConfigData.SatelliteConfigProto mConfigData;
 
     public SatelliteConfig(SatelliteConfigData.SatelliteConfigProto configData) {
@@ -61,13 +62,14 @@
         mSatelliteRegionCountryCodes = List.of(
                 mConfigData.deviceSatelliteRegion.countryCodes);
         mIsSatelliteRegionAllowed = mConfigData.deviceSatelliteRegion.isAllowed;
-        mSatS2FilePath = null;
+        mSatS2File = null;
 
         Log.d(TAG, "mVersion:" + mVersion + " | "
                 + "mSupportedServicesPerCarrier:" + mSupportedServicesPerCarrier + " | "
-                + "mSatelliteRegionCountryCodes:" + mSatelliteRegionCountryCodes + " | "
+                + "mSatelliteRegionCountryCodes:"
+                + String.join(",", mSatelliteRegionCountryCodes) + " | "
                 + "mIsSatelliteRegionAllowed:" + mIsSatelliteRegionAllowed + " | "
-                + "s2CellFile size:" + mConfigData.deviceSatelliteRegion.s2CellFile.length);
+                + " | s2CellFile size:" + mConfigData.deviceSatelliteRegion.s2CellFile.length);
     }
 
     /**
@@ -139,6 +141,19 @@
     }
 
     /**
+     * Get carrier identifier set for the satellite
+     *
+     * @return carrier identifier set from the config data.
+     */
+    @NonNull
+    public Set<Integer> getAllSatelliteCarrierIds() {
+        if (mSupportedServicesPerCarrier != null) {
+            return new ArraySet<>(mSupportedServicesPerCarrier.keySet());
+        }
+        return new ArraySet<>();
+    }
+
+    /**
      * @return satellite region country codes
      */
     @NonNull
@@ -167,23 +182,23 @@
      * @return satellite s2_cell_file path
      */
     @Nullable
-    public Path getSatelliteS2CellFile(@Nullable Context context) {
+    public File getSatelliteS2CellFile(@Nullable Context context) {
         if (context == null) {
             Log.d(TAG, "getSatelliteS2CellFile : context is null");
             return null;
         }
 
-        if (isFileExist(mSatS2FilePath)) {
-            Log.d(TAG, "File mSatS2FilePath is already exist");
-            return mSatS2FilePath;
+        if (isFileExist(mSatS2File)) {
+            Log.d(TAG, "File mSatS2File is already exist");
+            return mSatS2File;
         }
 
         if (mConfigData != null && mConfigData.deviceSatelliteRegion != null) {
-            mSatS2FilePath = copySatS2FileToPhoneDirectory(context,
-                    mConfigData.deviceSatelliteRegion.s2CellFile);
-            return mSatS2FilePath;
+            mSatS2File = copySatS2FileToPhoneDirectory(
+                    context, mConfigData.deviceSatelliteRegion.s2CellFile);
+            return mSatS2File;
         }
-        Log.d(TAG, "getSatelliteS2CellFile :"
+        Log.d(TAG, "getSatelliteS2CellFile: "
                 + "mConfigData is null or mConfigData.deviceSatelliteRegion is null");
         return null;
     }
@@ -195,7 +210,7 @@
      */
     @Nullable
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public Path copySatS2FileToPhoneDirectory(@Nullable Context context,
+    public File copySatS2FileToPhoneDirectory(@Nullable Context context,
             @Nullable byte[] byteArrayFile) {
 
         if (context == null || byteArrayFile == null) {
@@ -220,14 +235,18 @@
         } catch (IOException ex) {
             Log.e(TAG, "copySatS2FileToPhoneDirectory: ex=" + ex);
         }
-        return targetSatS2FilePath;
+        return targetSatS2FilePath.toFile();
     }
 
     /**
      * @return {@code true} if the SatS2File is already existed and {@code false} otherwise.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public boolean isFileExist(Path filePath) {
-        return Files.exists(filePath);
+    public boolean isFileExist(@Nullable File file) {
+        if (file == null) {
+            Log.d(TAG, "isFileExist : file is null");
+            return false;
+        }
+        return file.exists();
     }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java b/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java
new file mode 100644
index 0000000..384dfa5
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteConstants.java
@@ -0,0 +1,107 @@
+/*
+ * 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.satellite;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class SatelliteConstants {
+    public static final int CONFIG_DATA_SOURCE_UNKNOWN = 0;
+    public static final int CONFIG_DATA_SOURCE_ENTITLEMENT = 1;
+    public static final int CONFIG_DATA_SOURCE_CONFIG_UPDATER = 2;
+    public static final int CONFIG_DATA_SOURCE_CARRIER_CONFIG = 3;
+    public static final int CONFIG_DATA_SOURCE_DEVICE_CONFIG = 4;
+
+    @IntDef(prefix = {"CONFIG_DATA_SOURCE_"}, value = {
+            CONFIG_DATA_SOURCE_UNKNOWN,
+            CONFIG_DATA_SOURCE_ENTITLEMENT,
+            CONFIG_DATA_SOURCE_CONFIG_UPDATER,
+            CONFIG_DATA_SOURCE_CARRIER_CONFIG,
+            CONFIG_DATA_SOURCE_DEVICE_CONFIG
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ConfigDataSource {}
+
+    public static final int SATELLITE_ENTITLEMENT_STATUS_UNKNOWN = 0;
+    public static final int SATELLITE_ENTITLEMENT_STATUS_DISABLED = 1;
+    public static final int SATELLITE_ENTITLEMENT_STATUS_ENABLED = 2;
+    public static final int SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE = 3;
+    public static final int SATELLITE_ENTITLEMENT_STATUS_PROVISIONING = 4;
+
+    @IntDef(prefix = {"SATELLITE_ENTITLEMENT_STATUS_"}, value = {
+            SATELLITE_ENTITLEMENT_STATUS_UNKNOWN,
+            SATELLITE_ENTITLEMENT_STATUS_DISABLED,
+            SATELLITE_ENTITLEMENT_STATUS_ENABLED,
+            SATELLITE_ENTITLEMENT_STATUS_INCOMPATIBLE,
+            SATELLITE_ENTITLEMENT_STATUS_PROVISIONING
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SatelliteEntitlementStatus {}
+
+    public static final int CONFIG_UPDATE_RESULT_UNKNOWN = 0;
+    public static final int CONFIG_UPDATE_RESULT_SUCCESS = 1;
+    public static final int CONFIG_UPDATE_RESULT_INVALID_DOMAIN = 2;
+    public static final int CONFIG_UPDATE_RESULT_INVALID_VERSION = 3;
+    public static final int CONFIG_UPDATE_RESULT_NO_DATA = 4;
+    public static final int CONFIG_UPDATE_RESULT_NO_SATELLITE_DATA = 5;
+    public static final int CONFIG_UPDATE_RESULT_PARSE_ERROR = 6;
+    public static final int CONFIG_UPDATE_RESULT_CARRIER_DATA_INVALID_PLMN = 7;
+    public static final int CONFIG_UPDATE_RESULT_CARRIER_DATA_INVALID_SUPPORTED_SERVICES = 8;
+    public static final int CONFIG_UPDATE_RESULT_DEVICE_DATA_INVALID_COUNTRY_CODE = 9;
+    public static final int CONFIG_UPDATE_RESULT_DEVICE_DATA_INVALID_S2_CELL_FILE = 10;
+    public static final int CONFIG_UPDATE_RESULT_IO_ERROR = 11;
+
+    @IntDef(prefix = {"CONFIG_UPDATE_RESULT_"}, value = {
+            CONFIG_UPDATE_RESULT_UNKNOWN,
+            CONFIG_UPDATE_RESULT_SUCCESS,
+            CONFIG_UPDATE_RESULT_INVALID_DOMAIN,
+            CONFIG_UPDATE_RESULT_INVALID_VERSION,
+            CONFIG_UPDATE_RESULT_NO_DATA,
+            CONFIG_UPDATE_RESULT_NO_SATELLITE_DATA,
+            CONFIG_UPDATE_RESULT_PARSE_ERROR,
+            CONFIG_UPDATE_RESULT_CARRIER_DATA_INVALID_PLMN,
+            CONFIG_UPDATE_RESULT_CARRIER_DATA_INVALID_SUPPORTED_SERVICES,
+            CONFIG_UPDATE_RESULT_DEVICE_DATA_INVALID_COUNTRY_CODE,
+            CONFIG_UPDATE_RESULT_DEVICE_DATA_INVALID_S2_CELL_FILE,
+            CONFIG_UPDATE_RESULT_IO_ERROR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ConfigUpdateResult {}
+
+    // Access control type is unknown
+    public static final int ACCESS_CONTROL_TYPE_UNKNOWN = 0;
+    // Network country code is used for satellite access decision
+    public static final int ACCESS_CONTROL_TYPE_NETWORK_COUNTRY_CODE = 1;
+    // Device's current location is used for satellite access decision
+    public static final int ACCESS_CONTROL_TYPE_CURRENT_LOCATION = 2;
+    // Device's last known location is used for satellite access decision
+    public static final int ACCESS_CONTROL_TYPE_LAST_KNOWN_LOCATION = 3;
+    // Cached country codes are used for satellite access decision
+    public static final int ACCESS_CONTROL_TYPE_CACHED_COUNTRY_CODE = 4;
+
+    @IntDef(prefix = {"ACCESS_CONTROL_TYPE_"}, value = {
+            ACCESS_CONTROL_TYPE_UNKNOWN,
+            ACCESS_CONTROL_TYPE_NETWORK_COUNTRY_CODE,
+            ACCESS_CONTROL_TYPE_CURRENT_LOCATION,
+            ACCESS_CONTROL_TYPE_LAST_KNOWN_LOCATION,
+            ACCESS_CONTROL_TYPE_CACHED_COUNTRY_CODE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AccessControlType {}
+}
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 2c12939..f826f0b 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -17,7 +17,10 @@
 package com.android.internal.telephony.satellite;
 
 import static android.provider.Settings.ACTION_SATELLITE_SETTING;
+import static android.telephony.CarrierConfigManager.KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY;
 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;
@@ -50,6 +53,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.net.Uri;
@@ -77,7 +81,12 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.provider.Telephony;
+import android.telephony.AccessNetworkConstants;
 import android.telephony.CarrierConfigManager;
+import android.telephony.DropBoxManagerLoggerBackend;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.PersistentLogger;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
@@ -87,6 +96,7 @@
 import android.telephony.satellite.ISatelliteDatagramCallback;
 import android.telephony.satellite.ISatelliteModemStateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
+import android.telephony.satellite.ISatelliteSupportedStateCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteCapabilities;
@@ -110,10 +120,13 @@
 import com.android.internal.telephony.configupdate.ConfigProviderAdaptor;
 import com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver;
 import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.satellite.metrics.CarrierRoamingSatelliteControllerStats;
+import com.android.internal.telephony.satellite.metrics.CarrierRoamingSatelliteSessionStats;
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
 import com.android.internal.telephony.satellite.metrics.ProvisionMetricsStats;
 import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.internal.telephony.util.TelephonyUtils;
 import com.android.internal.util.FunctionalUtils;
 
 import java.util.ArrayList;
@@ -123,13 +136,15 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * Satellite controller is the backend service of
@@ -143,6 +158,7 @@
     private static final boolean DEBUG = !"user".equals(Build.TYPE);
     /** File used to store shared preferences related to satellite. */
     public static final String SATELLITE_SHARED_PREF = "satellite_shared_pref";
+    public static final String SATELLITE_SUBSCRIPTION_ID = "satellite_subscription_id";
     /** Value to pass for the setting key SATELLITE_MODE_ENABLED, enabled = 1, disabled = 0 */
     public static final int SATELLITE_MODE_ENABLED_TRUE = 1;
     public static final int SATELLITE_MODE_ENABLED_FALSE = 0;
@@ -152,11 +168,18 @@
      * to enable satellite.
      */
     public static final int TIMEOUT_TYPE_WAIT_FOR_SATELLITE_ENABLING_RESPONSE = 1;
+    /** This is used by CTS to override demo pointing aligned duration. */
+    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;
 
     /** 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";
 
+    public static final long DEFAULT_CARRIER_EMERGENCY_CALL_WAIT_FOR_CONNECTION_TIMEOUT_MILLIS =
+            TimeUnit.SECONDS.toMillis(30);
+
     /** 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;
@@ -175,8 +198,6 @@
     private static final int EVENT_IS_SATELLITE_SUPPORTED_DONE = 16;
     private static final int CMD_GET_SATELLITE_CAPABILITIES = 17;
     private static final int EVENT_GET_SATELLITE_CAPABILITIES_DONE = 18;
-    private static final int CMD_IS_SATELLITE_COMMUNICATION_ALLOWED = 19;
-    private static final int EVENT_IS_SATELLITE_COMMUNICATION_ALLOWED_DONE = 20;
     private static final int CMD_GET_TIME_SATELLITE_NEXT_VISIBLE = 21;
     private static final int EVENT_GET_TIME_SATELLITE_NEXT_VISIBLE_DONE = 22;
     private static final int EVENT_RADIO_STATE_CHANGED = 23;
@@ -197,15 +218,20 @@
     private static final int EVENT_SATELLITE_CAPABILITIES_CHANGED = 38;
     private 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;
 
     @NonNull private static SatelliteController sInstance;
     @NonNull private final Context mContext;
     @NonNull private final SatelliteModemInterface mSatelliteModemInterface;
-    @NonNull private SatelliteSessionController mSatelliteSessionController;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    @NonNull protected SatelliteSessionController mSatelliteSessionController;
     @NonNull private final PointingAppController mPointingAppController;
     @NonNull private final DatagramController mDatagramController;
     @NonNull private final ControllerMetricsStats mControllerMetricsStats;
     @NonNull private final ProvisionMetricsStats mProvisionMetricsStats;
+    @NonNull private SessionMetricsStats mSessionMetricsStats;
+    @NonNull private CarrierRoamingSatelliteControllerStats mCarrierRoamingSatelliteControllerStats;
     @NonNull private final SubscriptionManagerService mSubscriptionManagerService;
     private final CommandsInterface mCi;
     private ContentResolver mContentResolver;
@@ -250,7 +276,12 @@
     private final AtomicBoolean mRegisteredForNtnSignalStrengthChanged = new AtomicBoolean(false);
     private final AtomicBoolean mRegisteredForSatelliteCapabilitiesChanged =
             new AtomicBoolean(false);
-    private final AtomicBoolean mShouldReportNtnSignalStrength = new AtomicBoolean(false);
+    private final AtomicBoolean mIsModemEnabledReportingNtnSignalStrength =
+            new AtomicBoolean(false);
+    private final AtomicBoolean mLatestRequestedStateForNtnSignalStrengthReport =
+            new AtomicBoolean(false);
+    private final AtomicBoolean mRegisteredForSatelliteSupportedStateChanged =
+            new AtomicBoolean(false);
     /**
      * Map key: subId, value: callback to get error code of the provision request.
      */
@@ -274,13 +305,21 @@
      */
     private final ConcurrentHashMap<IBinder, ISatelliteCapabilitiesCallback>
             mSatelliteCapabilitiesChangedListeners = new ConcurrentHashMap<>();
+    /**
+     * Map key: binder of the callback, value: callback to receive supported state changed events.
+     */
+    private final ConcurrentHashMap<IBinder, ISatelliteSupportedStateCallback>
+            mSatelliteSupportedStateChangedListeners = new ConcurrentHashMap<>();
     private final Object mIsSatelliteSupportedLock = new Object();
     @GuardedBy("mIsSatelliteSupportedLock")
     private Boolean mIsSatelliteSupported = null;
     private boolean mIsDemoModeEnabled = false;
+    private boolean mIsEmergency = false;
     private final Object mIsSatelliteEnabledLock = new Object();
     @GuardedBy("mIsSatelliteEnabledLock")
     private Boolean mIsSatelliteEnabled = null;
+    private final Object mIsRadioOnLock = new Object();
+    @GuardedBy("mIsRadioOnLock")
     private boolean mIsRadioOn = false;
     private final Object mSatelliteViaOemProvisionLock = new Object();
     @GuardedBy("mSatelliteViaOemProvisionLock")
@@ -336,8 +375,22 @@
             mWasSatelliteConnectedViaCarrier = new SparseBooleanArray();
 
     @GuardedBy("mSatelliteConnectedLock")
-    @NonNull private final SparseBooleanArray
-            mIsSatelliteConnectedViaCarrierHysteresisTimeExpired = new SparseBooleanArray();
+    @NonNull private final SparseBooleanArray mLastNotifiedNtnMode = new SparseBooleanArray();
+
+    @GuardedBy("mSatelliteConnectedLock")
+    @NonNull private final SparseBooleanArray mInitialized = new SparseBooleanArray();
+
+    @GuardedBy("mSatelliteConnectedLock")
+    @NonNull private final Map<Integer, CarrierRoamingSatelliteSessionStats>
+            mCarrierRoamingSatelliteSessionStatsMap = new HashMap<>();
+
+    /**
+     * Key: Subscription ID; Value: set of
+     * {@link android.telephony.NetworkRegistrationInfo.ServiceType}
+     */
+    @GuardedBy("mSatelliteConnectedLock")
+    @NonNull private final Map<Integer, List<Integer>>
+            mSatModeCapabilitiesForCarrierRoaming = new HashMap<>();
 
     /**
      * This is used for testing only. When mEnforcedEmergencyCallToSatelliteHandoverType is valid,
@@ -349,6 +402,8 @@
     private int mDelayInSendingEventDisplayEmergencyMessage = 0;
     @NonNull private SharedPreferences mSharedPreferences = null;
 
+    @Nullable private PersistentLogger mPersistentLogger = null;
+
     /**
      * Key : Subscription ID, Value: {@code true} if the EntitlementStatus is enabled,
      * {@code false} otherwise.
@@ -358,6 +413,9 @@
     /** Key Subscription ID, value : PLMN allowed list from entitlement. */
     @GuardedBy("mSupportedSatelliteServicesLock")
     private SparseArray<List<String>> mEntitlementPlmnListPerCarrier = new SparseArray<>();
+    /** Key Subscription ID, value : PLMN barred list from entitlement. */
+    @GuardedBy("mSupportedSatelliteServicesLock")
+    private SparseArray<List<String>> mEntitlementBarredPlmnListPerCarrier = new SparseArray<>();
     /**
      * Key : Subscription ID, Value : If there is an entitlementPlmnList, use it. Otherwise, use the
      * carrierPlmnList. */
@@ -365,6 +423,13 @@
     private final SparseArray<List<String>> mMergedPlmnListPerCarrier = new SparseArray<>();
     private static AtomicLong sNextSatelliteEnableRequestId = new AtomicLong(0);
     private long mWaitTimeForSatelliteEnablingResponse;
+    private long mDemoPointingAlignedDurationMillis;
+    private long mDemoPointingNotAlignedDurationMillis;
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private long mLastEmergencyCallTime;
+    private long mSatelliteEmergencyModeDurationMillis;
+    private static final int DEFAULT_SATELLITE_EMERGENCY_MODE_DURATION_SECONDS = 300;
 
     /** Key used to read/write satellite system notification done in shared preferences. */
     private static final String SATELLITE_SYSTEM_NOTIFICATION_DONE_KEY =
@@ -377,6 +442,14 @@
     private static final String NOTIFICATION_CHANNEL_ID = "satellite";
 
     private final RegistrantList mSatelliteConfigUpdateChangedRegistrants = new RegistrantList();
+    private final BTWifiNFCStateReceiver mBTWifiNFCSateReceiver;
+    private final UwbAdapterStateCallback mUwbAdapterStateCallback;
+
+    private long mSessionStartTimeStamp;
+    private long mSessionProcessingTimeStamp;
+
+    // Variable for backup and restore device's screen rotation settings.
+    private String mDeviceRotationLockToBackupAndRestore = null;
 
     /**
      * @return The singleton instance of SatelliteController.
@@ -414,6 +487,11 @@
             @NonNull Context context, @NonNull Looper looper, @NonNull FeatureFlags featureFlags) {
         super(looper);
 
+        if (isSatellitePersistentLoggingEnabled(context, featureFlags)) {
+            mPersistentLogger = new PersistentLogger(
+                    DropBoxManagerLoggerBackend.getInstance(context));
+        }
+
         mContext = context;
         mFeatureFlags = featureFlags;
         Phone phone = SatelliteServiceUtils.getPhone();
@@ -421,24 +499,32 @@
         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);
+        mSatelliteModemInterface = SatelliteModemInterface.make(
+                mContext, this, mFeatureFlags);
 
         // Create the PointingUIController singleton,
         // which is used to manage interactions with PointingUI app.
-        mPointingAppController = PointingAppController.make(mContext);
+        mPointingAppController = PointingAppController.make(mContext, mFeatureFlags);
 
         // Create the SatelliteControllerMetrics to report controller metrics
         // should be called before making DatagramController
         mControllerMetricsStats = ControllerMetricsStats.make(mContext);
         mProvisionMetricsStats = ProvisionMetricsStats.getOrCreateInstance();
+        mSessionMetricsStats = SessionMetricsStats.getInstance();
+        mCarrierRoamingSatelliteControllerStats =
+                CarrierRoamingSatelliteControllerStats.getOrCreateInstance();
         mSubscriptionManagerService = SubscriptionManagerService.getInstance();
 
         // Create the DatagramController singleton,
         // which is used to send and receive satellite datagrams.
-        mDatagramController = DatagramController.make(mContext, looper, mPointingAppController);
+        mDatagramController = DatagramController.make(
+                mContext, looper, mFeatureFlags, mPointingAppController);
 
         mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
-        mIsRadioOn = phone.isRadioOn();
+        synchronized (mIsRadioOnLock) {
+            mIsRadioOn = phone.isRadioOn();
+        }
+
         registerForSatelliteProvisionStateChanged();
         registerForPendingDatagramCount();
         registerForSatelliteModemStateChanged();
@@ -446,6 +532,8 @@
         mContentResolver = mContext.getContentResolver();
         mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
 
+        mBTWifiNFCSateReceiver = new BTWifiNFCStateReceiver();
+        mUwbAdapterStateCallback = new UwbAdapterStateCallback();
         initializeSatelliteModeRadios();
 
         ContentObserver satelliteModeRadiosContentObserver = new ContentObserver(this) {
@@ -484,6 +572,11 @@
                 null);
         loadSatelliteSharedPreferences();
         mWaitTimeForSatelliteEnablingResponse = getWaitForSatelliteEnablingResponseTimeoutMillis();
+        mDemoPointingAlignedDurationMillis = getDemoPointingAlignedDurationMillisFromResources();
+        mDemoPointingNotAlignedDurationMillis =
+                getDemoPointingNotAlignedDurationMillisFromResources();
+        mSatelliteEmergencyModeDurationMillis =
+                getSatelliteEmergencyModeDurationFromOverlayConfig(context);
     }
 
     /**
@@ -509,11 +602,12 @@
      * Get satelliteConfig from SatelliteConfigParser
      */
     public SatelliteConfig getSatelliteConfig() {
-        if (getSatelliteConfigParser() == null) {
-            Log.d(TAG, "getSatelliteConfigParser() is not ready");
+        SatelliteConfigParser satelliteConfigParser = getSatelliteConfigParser();
+        if (satelliteConfigParser == null) {
+            Log.d(TAG, "satelliteConfigParser is not ready");
             return null;
         }
-        return (SatelliteConfig) getSatelliteConfigParser().getConfig();
+        return satelliteConfigParser.getConfig();
     }
 
     /**
@@ -528,8 +622,6 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected void initializeSatelliteModeRadios() {
         if (mContentResolver != null) {
-            BTWifiNFCStateReceiver bTWifiNFCSateReceiver = new BTWifiNFCStateReceiver();
-            UwbAdapterStateCallback uwbAdapterStateCallback = new UwbAdapterStateCallback();
             IntentFilter radioStateIntentFilter = new IntentFilter();
 
             synchronized (mRadioStateLock) {
@@ -548,10 +640,10 @@
                 String satelliteModeRadios = Settings.Global.getString(mContentResolver,
                         Settings.Global.SATELLITE_MODE_RADIOS);
                 if (satelliteModeRadios == null) {
-                    loge("initializeSatelliteModeRadios: satelliteModeRadios is null");
+                    ploge("initializeSatelliteModeRadios: satelliteModeRadios is null");
                     return;
                 }
-                logd("Radios To be checked when satellite is on: " + satelliteModeRadios);
+                plogd("Radios To be checked when satellite is on: " + satelliteModeRadios);
 
                 if (satelliteModeRadios.contains(Settings.Global.RADIO_BLUETOOTH)) {
                     BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -583,7 +675,14 @@
                         radioStateIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
                     }
                 }
-                mContext.registerReceiver(bTWifiNFCSateReceiver, radioStateIntentFilter);
+
+                try {
+                    // Unregister receiver before registering it.
+                    mContext.unregisterReceiver(mBTWifiNFCSateReceiver);
+                } catch (IllegalArgumentException e) {
+                    plogd("initializeSatelliteModeRadios: unregisterReceiver, e=" + e);
+                }
+                mContext.registerReceiver(mBTWifiNFCSateReceiver, radioStateIntentFilter);
 
                 if (satelliteModeRadios.contains(Settings.Global.RADIO_UWB)) {
                     UwbManager uwbManager = mContext.getSystemService(UwbManager.class);
@@ -592,20 +691,22 @@
                         mUwbStateEnabled = uwbManager.isUwbEnabled();
                         final long identity = Binder.clearCallingIdentity();
                         try {
+                            // Unregister callback before registering it.
+                            uwbManager.unregisterAdapterStateCallback(mUwbAdapterStateCallback);
                             uwbManager.registerAdapterStateCallback(mContext.getMainExecutor(),
-                                    uwbAdapterStateCallback);
+                                    mUwbAdapterStateCallback);
                         } finally {
                             Binder.restoreCallingIdentity(identity);
                         }
                     }
                 }
 
-                logd("mDisableBTOnSatelliteEnabled: " + mDisableBTOnSatelliteEnabled
+                plogd("mDisableBTOnSatelliteEnabled: " + mDisableBTOnSatelliteEnabled
                         + " mDisableNFCOnSatelliteEnabled: " + mDisableNFCOnSatelliteEnabled
                         + " mDisableWifiOnSatelliteEnabled: " + mDisableWifiOnSatelliteEnabled
                         + " mDisableUWBOnSatelliteEnabled: " + mDisableUWBOnSatelliteEnabled);
 
-                logd("mBTStateEnabled: " + mBTStateEnabled
+                plogd("mBTStateEnabled: " + mBTStateEnabled
                         + " mNfcStateEnabled: " + mNfcStateEnabled
                         + " mWifiStateEnabled: " + mWifiStateEnabled
                         + " mUwbStateEnabled: " + mUwbStateEnabled);
@@ -633,8 +734,8 @@
 
         @Override
         public void onStateChanged(int state, int reason) {
-            logd("UwbAdapterStateCallback#onStateChanged() called, state = " + toString(state));
-            logd("Adapter state changed reason " + String.valueOf(reason));
+            plogd("UwbAdapterStateCallback#onStateChanged() called, state = " + toString(state));
+            plogd("Adapter state changed reason " + String.valueOf(reason));
             synchronized (mRadioStateLock) {
                 if (state == UwbManager.AdapterStateCallback.STATE_DISABLED) {
                     mUwbStateEnabled = false;
@@ -642,7 +743,7 @@
                 } else {
                     mUwbStateEnabled = true;
                 }
-                logd("mUwbStateEnabled: " + mUwbStateEnabled);
+                plogd("mUwbStateEnabled: " + mUwbStateEnabled);
             }
         }
     }
@@ -652,7 +753,7 @@
         public void onReceive(Context context, Intent intent) {
             final String action = intent.getAction();
             if (action == null) {
-                logd("BTWifiNFCStateReceiver NULL action for intent " + intent);
+                plogd("BTWifiNFCStateReceiver NULL action for intent " + intent);
                 return;
             }
 
@@ -669,7 +770,7 @@
                             mBTStateEnabled = true;
                         }
                         if (currentBTStateEnabled != mBTStateEnabled) {
-                            logd("mBTStateEnabled=" + mBTStateEnabled);
+                            plogd("mBTStateEnabled=" + mBTStateEnabled);
                         }
                     }
                     break;
@@ -685,7 +786,7 @@
                             evaluateToSendSatelliteEnabledSuccess();
                         }
                         if (currentNfcStateEnabled != mNfcStateEnabled) {
-                            logd("mNfcStateEnabled=" + mNfcStateEnabled);
+                            plogd("mNfcStateEnabled=" + mNfcStateEnabled);
                         }
                     }
                     break;
@@ -702,7 +803,7 @@
                             evaluateToSendSatelliteEnabledSuccess();
                         }
                         if (currentWifiStateEnabled != mWifiStateEnabled) {
-                            logd("mWifiStateEnabled=" + mWifiStateEnabled);
+                            plogd("mWifiStateEnabled=" + mWifiStateEnabled);
                         }
                     }
                     break;
@@ -729,13 +830,15 @@
     private static final class RequestSatelliteEnabledArgument {
         public boolean enableSatellite;
         public boolean enableDemoMode;
+        public boolean isEmergency;
         @NonNull public Consumer<Integer> callback;
         public long requestId;
 
         RequestSatelliteEnabledArgument(boolean enableSatellite, boolean enableDemoMode,
-                Consumer<Integer> callback) {
+                boolean isEmergency, Consumer<Integer> callback) {
             this.enableSatellite = enableSatellite;
             this.enableDemoMode = enableDemoMode;
+            this.isEmergency = isEmergency;
             this.callback = callback;
             this.requestId = sNextSatelliteEnableRequestId.getAndUpdate(
                     n -> ((n + 1) % Long.MAX_VALUE));
@@ -889,14 +992,14 @@
                 RequestSatelliteEnabledArgument argument =
                         (RequestSatelliteEnabledArgument) request.argument;
                 int error =  SatelliteServiceUtils.getSatelliteError(ar, "setSatelliteEnabled");
-                logd("EVENT_SET_SATELLITE_ENABLED_DONE = " + error);
+                plogd("EVENT_SET_SATELLITE_ENABLED_DONE = " + error);
 
                 /*
                  * The timer to wait for EVENT_SET_SATELLITE_ENABLED_DONE might have expired and
                  * thus the request resources might have been cleaned up.
                  */
                 if (!shouldProcessEventSetSatelliteEnabledDone(argument)) {
-                    logw("The request ID=" + argument.requestId + ", enableSatellite="
+                    plogw("The request ID=" + argument.requestId + ", enableSatellite="
                             + argument.enableSatellite + " was already processed");
                     return;
                 }
@@ -908,6 +1011,7 @@
                             mWaitingForRadioDisabled = true;
                         }
                         setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_TRUE);
+                        setSettingsKeyToAllowDeviceRotation(SATELLITE_MODE_ENABLED_TRUE);
                         evaluateToSendSatelliteEnabledSuccess();
                     } else {
                         /**
@@ -934,12 +1038,15 @@
                                         SATELLITE_RESULT_SUCCESS,
                                         argument.callback);
                             } else {
-                                logd("Wait for satellite modem off before updating satellite"
+                                plogd("Wait for satellite modem off before updating satellite"
                                         + " modem state");
                             }
                             mWaitingForDisableSatelliteModemResponse = false;
                         }
                     }
+                    // Request Ntn signal strength report when satellite enabled or disabled done.
+                    mLatestRequestedStateForNtnSignalStrengthReport.set(argument.enableSatellite);
+                    updateNtnSignalStrengthReporting(argument.enableSatellite);
                 } else {
                     synchronized (mSatelliteEnabledRequestLock) {
                         if (mSatelliteEnabledRequest != null &&
@@ -951,6 +1058,7 @@
                                     SATELLITE_RESULT_SUCCESS);
                         }
                     }
+                    notifyEnablementFailedToSatelliteSessionController();
                     resetSatelliteEnabledRequest();
 
                     // If Satellite enable/disable request returned Error, no need to wait for radio
@@ -958,18 +1066,34 @@
                 }
 
                 if (argument.enableSatellite) {
+                    mSessionMetricsStats.setInitializationResult(error)
+                            .setSatelliteTechnology(getSupportedNtnRadioTechnology())
+                            .setInitializationProcessingTime(
+                                    System.currentTimeMillis() - mSessionProcessingTimeStamp)
+                            .setIsDemoMode(mIsDemoModeEnabled);
+                    mSessionProcessingTimeStamp = 0;
+
                     if (error == SATELLITE_RESULT_SUCCESS) {
                         mControllerMetricsStats.onSatelliteEnabled();
                         mControllerMetricsStats.reportServiceEnablementSuccessCount();
                     } else {
+                        mSessionMetricsStats.reportSessionMetrics();
+                        mSessionStartTimeStamp = 0;
                         mControllerMetricsStats.reportServiceEnablementFailCount();
                     }
-                    SessionMetricsStats.getInstance()
-                            .setInitializationResult(error)
-                            .setRadioTechnology(getSupportedNtnRadioTechnology())
-                            .reportSessionMetrics();
                 } else {
+                    mSessionMetricsStats.setTerminationResult(error)
+                            .setTerminationProcessingTime(System.currentTimeMillis()
+                                    - mSessionProcessingTimeStamp)
+                            .setSessionDurationSec(calculateSessionDurationTimeSec())
+                            .reportSessionMetrics();
+                    mSessionStartTimeStamp = 0;
+                    mSessionProcessingTimeStamp = 0;
+
                     mControllerMetricsStats.onSatelliteDisabled();
+
+                    handlePersistentLoggingOnSessionEnd(mIsEmergency);
+
                     synchronized (mIsSatelliteEnabledLock) {
                         mWaitingForDisableSatelliteModemResponse = false;
                     }
@@ -997,11 +1121,11 @@
                 Bundle bundle = new Bundle();
                 if (error == SATELLITE_RESULT_SUCCESS) {
                     if (ar.result == null) {
-                        loge("isSatelliteEnabled: result is null");
+                        ploge("isSatelliteEnabled: result is null");
                         error = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
                     } else {
                         boolean enabled = ((int[]) ar.result)[0] == 1;
-                        if (DBG) logd("isSatelliteEnabled: " + enabled);
+                        if (DBG) plogd("isSatelliteEnabled: " + enabled);
                         bundle.putBoolean(SatelliteManager.KEY_SATELLITE_ENABLED, enabled);
                         updateSatelliteEnabledState(enabled, "EVENT_IS_SATELLITE_ENABLED_DONE");
                     }
@@ -1026,11 +1150,11 @@
                 Bundle bundle = new Bundle();
                 if (error == SATELLITE_RESULT_SUCCESS) {
                     if (ar.result == null) {
-                        loge("isSatelliteSupported: result is null");
+                        ploge("isSatelliteSupported: result is null");
                         error = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
                     } else {
                         boolean supported = (boolean) ar.result;
-                        logd("isSatelliteSupported: " + supported);
+                        plogd("isSatelliteSupported: " + supported);
                         bundle.putBoolean(SatelliteManager.KEY_SATELLITE_SUPPORTED, supported);
                         updateSatelliteSupportedStateWhenSatelliteServiceConnected(supported);
                     }
@@ -1054,14 +1178,14 @@
                 Bundle bundle = new Bundle();
                 if (error == SATELLITE_RESULT_SUCCESS) {
                     if (ar.result == null) {
-                        loge("getSatelliteCapabilities: result is null");
+                        ploge("getSatelliteCapabilities: result is null");
                         error = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
                     } else {
                         SatelliteCapabilities capabilities = (SatelliteCapabilities) ar.result;
                         synchronized (mNeedsSatellitePointingLock) {
                             mNeedsSatellitePointing = capabilities.isPointingRequired();
                         }
-                        if (DBG) logd("getSatelliteCapabilities: " + capabilities);
+                        if (DBG) plogd("getSatelliteCapabilities: " + capabilities);
                         bundle.putParcelable(SatelliteManager.KEY_SATELLITE_CAPABILITIES,
                                 capabilities);
                         synchronized (mSatelliteCapabilitiesLock) {
@@ -1073,39 +1197,6 @@
                 break;
             }
 
-            case CMD_IS_SATELLITE_COMMUNICATION_ALLOWED: {
-                request = (SatelliteControllerHandlerRequest) msg.obj;
-                onCompleted =
-                        obtainMessage(EVENT_IS_SATELLITE_COMMUNICATION_ALLOWED_DONE, request);
-                mSatelliteModemInterface
-                        .requestIsSatelliteCommunicationAllowedForCurrentLocation(onCompleted);
-                break;
-            }
-
-            case EVENT_IS_SATELLITE_COMMUNICATION_ALLOWED_DONE: {
-                ar = (AsyncResult) msg.obj;
-                request = (SatelliteControllerHandlerRequest) ar.userObj;
-                int error =  SatelliteServiceUtils.getSatelliteError(ar,
-                        "isSatelliteCommunicationAllowedForCurrentLocation");
-                Bundle bundle = new Bundle();
-                if (error == SATELLITE_RESULT_SUCCESS) {
-                    if (ar.result == null) {
-                        loge("isSatelliteCommunicationAllowedForCurrentLocation: result is null");
-                        error = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
-                    } else {
-                        boolean communicationAllowed = (boolean) ar.result;
-                        if (DBG) {
-                            logd("isSatelliteCommunicationAllowedForCurrentLocation: "
-                                    + communicationAllowed);
-                        }
-                        bundle.putBoolean(SatelliteManager.KEY_SATELLITE_COMMUNICATION_ALLOWED,
-                                communicationAllowed);
-                    }
-                }
-                ((ResultReceiver) request.argument).send(error, bundle);
-                break;
-            }
-
             case CMD_GET_TIME_SATELLITE_NEXT_VISIBLE: {
                 request = (SatelliteControllerHandlerRequest) msg.obj;
                 onCompleted = obtainMessage(EVENT_GET_TIME_SATELLITE_NEXT_VISIBLE_DONE,
@@ -1122,13 +1213,13 @@
                 Bundle bundle = new Bundle();
                 if (error == SATELLITE_RESULT_SUCCESS) {
                     if (ar.result == null) {
-                        loge("requestTimeForNextSatelliteVisibility: result is null");
+                        ploge("requestTimeForNextSatelliteVisibility: result is null");
                         error = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
                     } else {
                         int nextVisibilityDuration = ((int[]) ar.result)[0];
                         if (DBG) {
-                            logd("requestTimeForNextSatelliteVisibility: " +
-                                    nextVisibilityDuration);
+                            plogd("requestTimeForNextSatelliteVisibility: "
+                                    + nextVisibilityDuration);
                         }
                         bundle.putInt(SatelliteManager.KEY_SATELLITE_NEXT_VISIBILITY,
                                 nextVisibilityDuration);
@@ -1139,9 +1230,15 @@
             }
 
             case EVENT_RADIO_STATE_CHANGED: {
-                if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON) {
-                    mIsRadioOn = true;
+                synchronized (mIsRadioOnLock) {
+                    logd("EVENT_RADIO_STATE_CHANGED: radioState=" + mCi.getRadioState());
+                    if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON) {
+                        mIsRadioOn = true;
+                    } else if (mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF) {
+                        resetCarrierRoamingSatelliteModeParams();
+                    }
                 }
+
                 if (mCi.getRadioState() != TelephonyManager.RADIO_POWER_UNAVAILABLE) {
                     if (mSatelliteModemInterface.isSatelliteServiceConnected()) {
                         synchronized (mIsSatelliteSupportedLock) {
@@ -1150,7 +1247,7 @@
                                     @Override
                                     protected void onReceiveResult(
                                             int resultCode, Bundle resultData) {
-                                        logd("onRadioStateChanged.requestIsSatelliteSupported: "
+                                        plogd("onRadioStateChanged.requestIsSatelliteSupported: "
                                                 + "resultCode=" + resultCode
                                                 + ", resultData=" + resultData);
                                     }
@@ -1178,18 +1275,18 @@
             case EVENT_SATELLITE_PROVISION_STATE_CHANGED:
                 ar = (AsyncResult) msg.obj;
                 if (ar.result == null) {
-                    loge("EVENT_SATELLITE_PROVISION_STATE_CHANGED: result is null");
+                    ploge("EVENT_SATELLITE_PROVISION_STATE_CHANGED: result is null");
                 } else {
                     handleEventSatelliteProvisionStateChanged((boolean) ar.result);
                 }
                 break;
 
             case EVENT_PENDING_DATAGRAMS:
-                logd("Received EVENT_PENDING_DATAGRAMS");
+                plogd("Received EVENT_PENDING_DATAGRAMS");
                 IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
                     @Override
                     public void accept(int result) {
-                        logd("pollPendingSatelliteDatagram result: " + result);
+                        plogd("pollPendingSatelliteDatagram result: " + result);
                     }
                 };
                 pollPendingDatagrams(
@@ -1199,7 +1296,7 @@
             case EVENT_SATELLITE_MODEM_STATE_CHANGED:
                 ar = (AsyncResult) msg.obj;
                 if (ar.result == null) {
-                    loge("EVENT_SATELLITE_MODEM_STATE_CHANGED: result is null");
+                    ploge("EVENT_SATELLITE_MODEM_STATE_CHANGED: result is null");
                 } else {
                     handleEventSatelliteModemStateChanged((int) ar.result);
                 }
@@ -1210,7 +1307,7 @@
                 break;
 
             case CMD_EVALUATE_SATELLITE_ATTACH_RESTRICTION_CHANGE: {
-                logd("CMD_EVALUATE_SATELLITE_ATTACH_RESTRICTION_CHANGE");
+                plogd("CMD_EVALUATE_SATELLITE_ATTACH_RESTRICTION_CHANGE");
                 request = (SatelliteControllerHandlerRequest) msg.obj;
                 handleRequestSatelliteAttachRestrictionForCarrierCmd(request);
                 break;
@@ -1241,7 +1338,7 @@
             }
 
             case CMD_REQUEST_NTN_SIGNAL_STRENGTH: {
-                logd("CMD_REQUEST_NTN_SIGNAL_STRENGTH");
+                plogd("CMD_REQUEST_NTN_SIGNAL_STRENGTH");
                 request = (SatelliteControllerHandlerRequest) msg.obj;
                 onCompleted = obtainMessage(EVENT_REQUEST_NTN_SIGNAL_STRENGTH_DONE, request);
                 mSatelliteModemInterface.requestNtnSignalStrength(onCompleted);
@@ -1270,7 +1367,7 @@
                                         NTN_SIGNAL_STRENGTH_NONE);
                             }
                         }
-                        loge("EVENT_REQUEST_NTN_SIGNAL_STRENGTH_DONE: ntnSignalStrength is null");
+                        ploge("EVENT_REQUEST_NTN_SIGNAL_STRENGTH_DONE: ntnSignalStrength is null");
                         result.send(SatelliteManager.SATELLITE_RESULT_REQUEST_FAILED, null);
                     }
                 } else {
@@ -1287,7 +1384,7 @@
             case EVENT_NTN_SIGNAL_STRENGTH_CHANGED: {
                 ar = (AsyncResult) msg.obj;
                 if (ar.result == null) {
-                    loge("EVENT_NTN_SIGNAL_STRENGTH_CHANGED: result is null");
+                    ploge("EVENT_NTN_SIGNAL_STRENGTH_CHANGED: result is null");
                 } else {
                     handleEventNtnSignalStrengthChanged((NtnSignalStrength) ar.result);
                 }
@@ -1298,27 +1395,9 @@
                 ar = (AsyncResult) msg.obj;
                 boolean shouldReport = (boolean) ar.result;
                 if (DBG) {
-                    logd("CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING: shouldReport=" + shouldReport);
+                    plogd("CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING: shouldReport=" + shouldReport);
                 }
-                request = new SatelliteControllerHandlerRequest(shouldReport,
-                        SatelliteServiceUtils.getPhone());
-                if (SATELLITE_RESULT_SUCCESS != evaluateOemSatelliteRequestAllowed(true)) {
-                    return;
-                }
-                if (mShouldReportNtnSignalStrength.get() == shouldReport) {
-                    if (DBG) {
-                        logd("CMD_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING : modem state matches the "
-                                + "expected state, return.");
-                    }
-                    return;
-                }
-                onCompleted = obtainMessage(EVENT_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING_DONE,
-                        request);
-                if (shouldReport) {
-                    mSatelliteModemInterface.startSendingNtnSignalStrength(onCompleted);
-                } else {
-                    mSatelliteModemInterface.stopSendingNtnSignalStrength(onCompleted);
-                }
+                handleCmdUpdateNtnSignalStrengthReporting(shouldReport);
                 break;
             }
 
@@ -1327,9 +1406,17 @@
                 request = (SatelliteControllerHandlerRequest) ar.userObj;
                 boolean shouldReport = (boolean) request.argument;
                 int errorCode =  SatelliteServiceUtils.getSatelliteError(ar,
-                        "EVENT_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING_DONE");
+                        "EVENT_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING_DONE: shouldReport="
+                                + shouldReport);
                 if (errorCode == SATELLITE_RESULT_SUCCESS) {
-                    mShouldReportNtnSignalStrength.set(shouldReport);
+                    mIsModemEnabledReportingNtnSignalStrength.set(shouldReport);
+                    if (mLatestRequestedStateForNtnSignalStrengthReport.get()
+                            != mIsModemEnabledReportingNtnSignalStrength.get()) {
+                        logd("mLatestRequestedStateForNtnSignalStrengthReport does not match with "
+                                + "mIsModemEnabledReportingNtnSignalStrength");
+                        updateNtnSignalStrengthReporting(
+                                mLatestRequestedStateForNtnSignalStrengthReport.get());
+                    }
                 } else {
                     loge(((boolean) request.argument ? "startSendingNtnSignalStrength"
                             : "stopSendingNtnSignalStrength") + "returns " + errorCode);
@@ -1345,19 +1432,36 @@
             case EVENT_SATELLITE_CAPABILITIES_CHANGED: {
                 ar = (AsyncResult) msg.obj;
                 if (ar.result == null) {
-                    loge("EVENT_SATELLITE_CAPABILITIES_CHANGED: result is null");
+                    ploge("EVENT_SATELLITE_CAPABILITIES_CHANGED: result is null");
                 } else {
                     handleEventSatelliteCapabilitiesChanged((SatelliteCapabilities) ar.result);
                 }
                 break;
             }
 
+            case EVENT_SATELLITE_SUPPORTED_STATE_CHANGED: {
+                ar = (AsyncResult) msg.obj;
+                if (ar.result == null) {
+                    ploge("EVENT_SATELLITE_SUPPORTED_STATE_CHANGED: result is null");
+                } else {
+                    handleEventSatelliteSupportedStateChanged((boolean) ar.result);
+                }
+                break;
+            }
+
             case EVENT_SATELLITE_CONFIG_DATA_UPDATED: {
                 handleEventConfigDataUpdated();
                 mSatelliteConfigUpdateChangedRegistrants.notifyRegistrants();
                 break;
             }
 
+            case EVENT_NOTIFY_NTN_HYSTERESIS_TIMED_OUT: {
+                int phoneId = (int) msg.obj;
+                Phone phone = PhoneFactory.getPhone(phoneId);
+                updateLastNotifiedNtnModeAndNotify(phone);
+                break;
+            }
+
             default:
                 Log.w(TAG, "SatelliteControllerHandler: unexpected message code: " +
                         msg.what);
@@ -1373,7 +1477,7 @@
                 processNewCarrierConfigData(subId);
             }
         } else {
-            loge("updateSupportedSatelliteServicesForActiveSubscriptions: "
+            ploge("updateSupportedSatelliteServicesForActiveSubscriptions: "
                     + "activeSubIds is null");
         }
     }
@@ -1393,12 +1497,13 @@
      * @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,
-            @NonNull IIntegerConsumer callback) {
-        logd("requestSatelliteEnabled subId: " + subId + " enableSatellite: " + enableSatellite
-                + " enableDemoMode: " + enableDemoMode);
+            boolean isEmergency, @NonNull IIntegerConsumer callback) {
+        plogd("requestSatelliteEnabled subId: " + subId + " enableSatellite: " + enableSatellite
+                + " enableDemoMode: " + enableDemoMode + " isEmergency: " + isEmergency);
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
         int error = evaluateOemSatelliteRequestAllowed(true);
         if (error != SATELLITE_RESULT_SUCCESS) {
@@ -1407,11 +1512,13 @@
         }
 
         if (enableSatellite) {
-            if (!mIsRadioOn) {
-                loge("Radio is not on, can not enable satellite");
-                sendErrorAndReportSessionMetrics(
-                        SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE, result);
-                return;
+            synchronized (mIsRadioOnLock) {
+                if (!mIsRadioOn) {
+                    ploge("Radio is not on, can not enable satellite");
+                    sendErrorAndReportSessionMetrics(
+                            SatelliteManager.SATELLITE_RESULT_INVALID_MODEM_STATE, result);
+                    return;
+                }
             }
         } else {
             /* if disable satellite, always assume demo is also disabled */
@@ -1422,13 +1529,13 @@
             if (mIsSatelliteEnabled != null) {
                 if (mIsSatelliteEnabled == enableSatellite) {
                     if (enableDemoMode != mIsDemoModeEnabled) {
-                        loge("Received invalid demo mode while satellite session is enabled"
+                        ploge("Received invalid demo mode while satellite session is enabled"
                                 + " enableDemoMode = " + enableDemoMode);
                         sendErrorAndReportSessionMetrics(
                                 SatelliteManager.SATELLITE_RESULT_INVALID_ARGUMENTS, result);
                         return;
                     } else {
-                        logd("Enable request matches with current state"
+                        plogd("Enable request matches with current state"
                                 + " enableSatellite = " + enableSatellite);
                         sendErrorAndReportSessionMetrics(
                                 SatelliteManager.SATELLITE_RESULT_SUCCESS, result);
@@ -1439,7 +1546,8 @@
         }
 
         RequestSatelliteEnabledArgument request =
-                new RequestSatelliteEnabledArgument(enableSatellite, enableDemoMode, result);
+                new RequestSatelliteEnabledArgument(enableSatellite, enableDemoMode, isEmergency,
+                        result);
         /**
          * Multiple satellite enabled requests are handled as below:
          * 1. If there are no ongoing requests, store current request in mSatelliteEnabledRequest
@@ -1454,14 +1562,14 @@
             if (mSatelliteEnabledRequest == null) {
                 mSatelliteEnabledRequest = request;
             } else if (mSatelliteEnabledRequest.enableSatellite == request.enableSatellite) {
-                logd("requestSatelliteEnabled enableSatellite: " + enableSatellite
+                plogd("requestSatelliteEnabled enableSatellite: " + enableSatellite
                         + " is already in progress.");
                 sendErrorAndReportSessionMetrics(
                         SatelliteManager.SATELLITE_RESULT_REQUEST_IN_PROGRESS, result);
                 return;
             } else if (mSatelliteEnabledRequest.enableSatellite == false
                     && request.enableSatellite == true) {
-                logd("requestSatelliteEnabled enableSatellite: " + enableSatellite + " cannot be "
+                plogd("requestSatelliteEnabled enableSatellite: " + enableSatellite + " cannot be "
                         + "processed. Disable satellite is already in progress.");
                 sendErrorAndReportSessionMetrics(
                         SatelliteManager.SATELLITE_RESULT_ERROR, result);
@@ -1507,7 +1615,7 @@
      */
     public boolean isSatelliteEnabled() {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("isSatelliteEnabled: oemEnabledSatelliteFlag is disabled");
+            plogd("isSatelliteEnabled: oemEnabledSatelliteFlag is disabled");
             return false;
         }
         if (mIsSatelliteEnabled == null) return false;
@@ -1515,6 +1623,23 @@
     }
 
     /**
+     * Get whether satellite modem is being enabled.
+     *
+     * @return {@code true} if the satellite modem is being enabled and {@code false} otherwise.
+     */
+    public boolean isSatelliteBeingEnabled() {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            plogd("isSatelliteBeingEnabled: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
+
+        if (mSatelliteSessionController != null) {
+            return mSatelliteSessionController.isInEnablingState();
+        }
+        return false;
+    }
+
+    /**
      * 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
@@ -1541,7 +1666,7 @@
      */
     public boolean isDemoModeEnabled() {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("isDemoModeEnabled: oemEnabledSatelliteFlag is disabled");
+            plogd("isDemoModeEnabled: oemEnabledSatelliteFlag is disabled");
             return false;
         }
         return mIsDemoModeEnabled;
@@ -1556,7 +1681,7 @@
      */
     public void requestIsSatelliteSupported(int subId, @NonNull ResultReceiver result) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("requestIsSatelliteSupported: oemEnabledSatelliteFlag is disabled");
+            plogd("requestIsSatelliteSupported: oemEnabledSatelliteFlag is disabled");
             result.send(SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED, null);
             return;
         }
@@ -1565,6 +1690,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);
                 result.send(SATELLITE_RESULT_SUCCESS, bundle);
                 return;
             }
@@ -1768,7 +1894,7 @@
     public void unregisterForSatelliteProvisionStateChanged(
             int subId, @NonNull ISatelliteProvisionStateCallback callback) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("unregisterForSatelliteProvisionStateChanged: "
+            plogd("unregisterForSatelliteProvisionStateChanged: "
                     + "oemEnabledSatelliteFlag is disabled");
             return;
         }
@@ -1814,13 +1940,13 @@
     @SatelliteManager.SatelliteResult public int registerForSatelliteModemStateChanged(int subId,
             @NonNull ISatelliteModemStateCallback callback) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("registerForSatelliteModemStateChanged: oemEnabledSatelliteFlag is disabled");
+            plogd("registerForSatelliteModemStateChanged: oemEnabledSatelliteFlag is disabled");
             return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
         }
         if (mSatelliteSessionController != null) {
             mSatelliteSessionController.registerForSatelliteModemStateChanged(callback);
         } else {
-            loge("registerForSatelliteModemStateChanged: mSatelliteSessionController"
+            ploge("registerForSatelliteModemStateChanged: mSatelliteSessionController"
                     + " is not initialized yet");
             return SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
         }
@@ -1838,13 +1964,13 @@
     public void unregisterForModemStateChanged(int subId,
             @NonNull ISatelliteModemStateCallback callback) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("unregisterForModemStateChanged: oemEnabledSatelliteFlag is disabled");
+            plogd("unregisterForModemStateChanged: oemEnabledSatelliteFlag is disabled");
             return;
         }
         if (mSatelliteSessionController != null) {
             mSatelliteSessionController.unregisterForSatelliteModemStateChanged(callback);
         } else {
-            loge("unregisterForModemStateChanged: mSatelliteSessionController"
+            ploge("unregisterForModemStateChanged: mSatelliteSessionController"
                     + " is not initialized yet");
         }
     }
@@ -1860,13 +1986,13 @@
     @SatelliteManager.SatelliteResult public int registerForIncomingDatagram(int subId,
             @NonNull ISatelliteDatagramCallback callback) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("registerForIncomingDatagram: oemEnabledSatelliteFlag is disabled");
+            plogd("registerForIncomingDatagram: oemEnabledSatelliteFlag is disabled");
             return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
         }
         if (!mSatelliteModemInterface.isSatelliteServiceSupported()) {
             return SatelliteManager.SATELLITE_RESULT_NOT_SUPPORTED;
         }
-        logd("registerForIncomingDatagram: callback=" + callback);
+        plogd("registerForIncomingDatagram: callback=" + callback);
         return mDatagramController.registerForSatelliteDatagram(subId, callback);
     }
 
@@ -1881,13 +2007,13 @@
     public void unregisterForIncomingDatagram(int subId,
             @NonNull ISatelliteDatagramCallback callback) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("unregisterForIncomingDatagram: oemEnabledSatelliteFlag is disabled");
+            plogd("unregisterForIncomingDatagram: oemEnabledSatelliteFlag is disabled");
             return;
         }
         if (!mSatelliteModemInterface.isSatelliteServiceSupported()) {
             return;
         }
-        logd("unregisterForIncomingDatagram: callback=" + callback);
+        plogd("unregisterForIncomingDatagram: callback=" + callback);
         mDatagramController.unregisterForSatelliteDatagram(subId, callback);
     }
 
@@ -1933,7 +2059,7 @@
     public void sendDatagram(int subId, @SatelliteManager.DatagramType int datagramType,
             SatelliteDatagram datagram, boolean needFullScreenPointingUI,
             @NonNull IIntegerConsumer callback) {
-        logd("sendSatelliteDatagram: subId: " + subId + " datagramType: " + datagramType
+        plogd("sendSatelliteDatagram: subId: " + subId + " datagramType: " + datagramType
                 + " needFullScreenPointingUI: " + needFullScreenPointingUI);
 
         Consumer<Integer> result = FunctionalUtils.ignoreRemoteException(callback::accept);
@@ -1947,7 +2073,9 @@
          * TODO for NTN-based satellites: Check if satellite is acquired.
          */
         if (mNeedsSatellitePointing) {
-            mPointingAppController.startPointingUI(needFullScreenPointingUI);
+
+            mPointingAppController.startPointingUI(needFullScreenPointingUI, mIsDemoModeEnabled,
+                    mIsEmergency);
         }
 
         final int validSubId = SatelliteServiceUtils.getValidSatelliteSubId(subId, mContext);
@@ -1956,26 +2084,6 @@
     }
 
     /**
-     * Request to get whether satellite communication is allowed for the current location.
-     *
-     * @param subId The subId of the subscription to check whether satellite communication is
-     *              allowed for the current location for.
-     * @param result The result receiver that returns whether satellite communication is allowed
-     *               for the current location if the request is successful or an error code
-     *               if the request failed.
-     */
-    public void requestIsSatelliteCommunicationAllowedForCurrentLocation(int subId,
-            @NonNull ResultReceiver result) {
-        int error = evaluateOemSatelliteRequestAllowed(false);
-        if (error != SATELLITE_RESULT_SUCCESS) {
-            result.send(error, null);
-            return;
-        }
-
-        sendRequestAsync(CMD_IS_SATELLITE_COMMUNICATION_ALLOWED, result, null);
-    }
-
-    /**
      * 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.
@@ -2000,9 +2108,11 @@
      */
     public void setDeviceAlignedWithSatellite(@NonNull int subId, @NonNull boolean isAligned) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("setDeviceAlignedWithSatellite: oemEnabledSatelliteFlag is disabled");
+            plogd("setDeviceAlignedWithSatellite: oemEnabledSatelliteFlag is disabled");
             return;
         }
+
+        DemoSimulator.getInstance().setDeviceAlignedWithSatellite(isAligned);
         mDatagramController.setDeviceAlignedWithSatellite(isAligned);
     }
 
@@ -2115,7 +2225,7 @@
      * strength of the satellite connection.
      */
     public void requestNtnSignalStrength(int subId, @NonNull ResultReceiver result) {
-        if (DBG) logd("requestNtnSignalStrength()");
+        if (DBG) plogd("requestNtnSignalStrength()");
 
         int error = evaluateOemSatelliteRequestAllowed(true);
         if (error != SATELLITE_RESULT_SUCCESS) {
@@ -2154,7 +2264,7 @@
      */
     public void registerForNtnSignalStrengthChanged(int subId,
             @NonNull INtnSignalStrengthCallback callback) throws RemoteException {
-        if (DBG) logd("registerForNtnSignalStrengthChanged()");
+        if (DBG) plogd("registerForNtnSignalStrengthChanged()");
 
         int error = evaluateOemSatelliteRequestAllowed(true);
         if (error == SATELLITE_RESULT_SUCCESS) {
@@ -2175,7 +2285,7 @@
      */
     public void unregisterForNtnSignalStrengthChanged(
             int subId, @NonNull INtnSignalStrengthCallback callback) {
-        if (DBG) logd("unregisterForNtnSignalStrengthChanged()");
+        if (DBG) plogd("unregisterForNtnSignalStrengthChanged()");
 
         int error = evaluateOemSatelliteRequestAllowed(true);
         if (error == SATELLITE_RESULT_SUCCESS) {
@@ -2193,7 +2303,7 @@
      */
     @SatelliteManager.SatelliteResult public int registerForCapabilitiesChanged(
             int subId, @NonNull ISatelliteCapabilitiesCallback callback) {
-        if (DBG) logd("registerForCapabilitiesChanged()");
+        if (DBG) plogd("registerForCapabilitiesChanged()");
 
         int error = evaluateOemSatelliteRequestAllowed(true);
         if (error != SATELLITE_RESULT_SUCCESS) return error;
@@ -2213,7 +2323,7 @@
      */
     public void unregisterForCapabilitiesChanged(
             int subId, @NonNull ISatelliteCapabilitiesCallback callback) {
-        if (DBG) logd("unregisterForCapabilitiesChanged()");
+        if (DBG) plogd("unregisterForCapabilitiesChanged()");
 
         int error = evaluateOemSatelliteRequestAllowed(true);
         if (error == SATELLITE_RESULT_SUCCESS) {
@@ -2222,6 +2332,43 @@
     }
 
     /**
+     * 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) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            plogd("registerForSatelliteSupportedStateChanged: oemEnabledSatelliteFlag is disabled");
+            return SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
+        }
+
+        mSatelliteSupportedStateChangedListeners.put(callback.asBinder(), callback);
+        return SATELLITE_RESULT_SUCCESS;
+    }
+
+    /**
+     * 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) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            plogd("unregisterForSatelliteSupportedStateChanged: "
+                    + "oemEnabledSatelliteFlag is disabled");
+            return;
+        }
+        mSatelliteSupportedStateChangedListeners.remove(callback.asBinder());
+    }
+
+    /**
      * This API can be used by only CTS to update satellite vendor service package name.
      *
      * @param servicePackageName The package name of the satellite vendor service.
@@ -2230,16 +2377,16 @@
      */
     public boolean setSatelliteServicePackageName(@Nullable String servicePackageName) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("setSatelliteServicePackageName: oemEnabledSatelliteFlag is disabled");
+            plogd("setSatelliteServicePackageName: oemEnabledSatelliteFlag is disabled");
             return false;
         }
         if (!isMockModemAllowed()) {
-            logd("setSatelliteServicePackageName: mock modem not allowed");
+            plogd("setSatelliteServicePackageName: mock modem not allowed");
             return false;
         }
 
         // Cached states need to be cleared whenever switching satellite vendor services.
-        logd("setSatelliteServicePackageName: Resetting cached states");
+        plogd("setSatelliteServicePackageName: Resetting cached states");
         synchronized (mIsSatelliteSupportedLock) {
             mIsSatelliteSupported = null;
         }
@@ -2266,11 +2413,11 @@
      */
     public boolean setSatelliteListeningTimeoutDuration(long timeoutMillis) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("setSatelliteListeningTimeoutDuration: oemEnabledSatelliteFlag is disabled");
+            plogd("setSatelliteListeningTimeoutDuration: oemEnabledSatelliteFlag is disabled");
             return false;
         }
         if (mSatelliteSessionController == null) {
-            loge("mSatelliteSessionController is not initialized yet");
+            ploge("mSatelliteSessionController is not initialized yet");
             return false;
         }
         return mSatelliteSessionController.setSatelliteListeningTimeoutDuration(timeoutMillis);
@@ -2286,16 +2433,35 @@
     public boolean setDatagramControllerTimeoutDuration(
             boolean reset, int timeoutType, long timeoutMillis) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("setDatagramControllerTimeoutDuration: oemEnabledSatelliteFlag is disabled");
+            plogd("setDatagramControllerTimeoutDuration: oemEnabledSatelliteFlag is disabled");
             return false;
         }
-        logd("setDatagramControllerTimeoutDuration: reset=" + reset + ", timeoutType="
+        plogd("setDatagramControllerTimeoutDuration: reset=" + reset + ", timeoutType="
                 + timeoutType + ", timeoutMillis=" + timeoutMillis);
         return mDatagramController.setDatagramControllerTimeoutDuration(
                 reset, timeoutType, timeoutMillis);
     }
 
     /**
+     * This API can be used by only CTS to override the boolean configs used by the
+     * DatagramController module.
+     *
+     * @param enable Whether to enable or disable boolean config.
+     * @return {@code true} if the boolean config is set successfully, {@code false} otherwise.
+     */
+    public boolean setDatagramControllerBooleanConfig(
+            boolean reset, int booleanType, boolean enable) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            logd("setDatagramControllerBooleanConfig: oemEnabledSatelliteFlag is disabled");
+            return false;
+        }
+        logd("setDatagramControllerBooleanConfig: reset=" + reset + ", booleanType="
+                + booleanType + ", enable=" + enable);
+        return mDatagramController.setDatagramControllerBooleanConfig(
+                reset, booleanType, enable);
+    }
+
+    /**
      * This API can be used by only CTS to override timeout durations used by SatelliteController
      * module.
      *
@@ -2305,14 +2471,14 @@
     public boolean setSatelliteControllerTimeoutDuration(
             boolean reset, int timeoutType, long timeoutMillis) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("setSatelliteControllerTimeoutDuration: oemEnabledSatelliteFlag is disabled");
+            plogd("setSatelliteControllerTimeoutDuration: oemEnabledSatelliteFlag is disabled");
             return false;
         }
         if (!isMockModemAllowed()) {
-            logd("setSatelliteControllerTimeoutDuration: mock modem is not allowed");
+            plogd("setSatelliteControllerTimeoutDuration: mock modem is not allowed");
             return false;
         }
-        logd("setSatelliteControllerTimeoutDuration: reset=" + reset + ", timeoutType="
+        plogd("setSatelliteControllerTimeoutDuration: reset=" + reset + ", timeoutType="
                 + timeoutType + ", timeoutMillis=" + timeoutMillis);
         if (timeoutType == TIMEOUT_TYPE_WAIT_FOR_SATELLITE_ENABLING_RESPONSE) {
             if (reset) {
@@ -2321,9 +2487,23 @@
             } else {
                 mWaitTimeForSatelliteEnablingResponse = timeoutMillis;
             }
-            logd("mWaitTimeForSatelliteEnablingResponse=" + mWaitTimeForSatelliteEnablingResponse);
+            plogd("mWaitTimeForSatelliteEnablingResponse=" + mWaitTimeForSatelliteEnablingResponse);
+        } else if (timeoutType == TIMEOUT_TYPE_DEMO_POINTING_ALIGNED_DURATION_MILLIS) {
+            if (reset) {
+                mDemoPointingAlignedDurationMillis =
+                        getDemoPointingAlignedDurationMillisFromResources();
+            } else {
+                mDemoPointingAlignedDurationMillis = timeoutMillis;
+            }
+        } else if (timeoutType == TIMEOUT_TYPE_DEMO_POINTING_NOT_ALIGNED_DURATION_MILLIS) {
+            if (reset) {
+                mDemoPointingNotAlignedDurationMillis =
+                        getDemoPointingNotAlignedDurationMillisFromResources();
+            } else {
+                mDemoPointingNotAlignedDurationMillis = timeoutMillis;
+            }
         } else {
-            logw("Invalid timeoutType=" + timeoutType);
+            plogw("Invalid timeoutType=" + timeoutType);
             return false;
         }
         return true;
@@ -2338,11 +2518,11 @@
      */
     public boolean setSatelliteGatewayServicePackageName(@Nullable String servicePackageName) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("setSatelliteGatewayServicePackageName: oemEnabledSatelliteFlag is disabled");
+            plogd("setSatelliteGatewayServicePackageName: oemEnabledSatelliteFlag is disabled");
             return false;
         }
         if (mSatelliteSessionController == null) {
-            loge("mSatelliteSessionController is not initialized yet");
+            ploge("mSatelliteSessionController is not initialized yet");
             return false;
         }
         return mSatelliteSessionController.setSatelliteGatewayServicePackageName(
@@ -2360,7 +2540,7 @@
     public boolean setSatellitePointingUiClassName(
             @Nullable String packageName, @Nullable String className) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("setSatellitePointingUiClassName: oemEnabledSatelliteFlag is disabled");
+            plogd("setSatellitePointingUiClassName: oemEnabledSatelliteFlag is disabled");
             return false;
         }
         return mPointingAppController.setSatellitePointingUiClassName(packageName, className);
@@ -2381,7 +2561,7 @@
      */
     public boolean setEmergencyCallToSatelliteHandoverType(int handoverType, int delaySeconds) {
         if (!isMockModemAllowed()) {
-            loge("setEmergencyCallToSatelliteHandoverType: mock modem not allowed");
+            ploge("setEmergencyCallToSatelliteHandoverType: mock modem not allowed");
             return false;
         }
         if (isHandoverTypeValid(handoverType)) {
@@ -2405,7 +2585,7 @@
      */
     public boolean setOemEnabledSatelliteProvisionStatus(boolean reset, boolean isProvisioned) {
         if (!isMockModemAllowed()) {
-            loge("setOemEnabledSatelliteProvisionStatus: mock modem not allowed");
+            ploge("setOemEnabledSatelliteProvisionStatus: mock modem not allowed");
             return false;
         }
         synchronized (mSatelliteViaOemProvisionLock) {
@@ -2450,7 +2630,7 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void onSatelliteServiceConnected() {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("onSatelliteServiceConnected: oemEnabledSatelliteFlag is disabled");
+            plogd("onSatelliteServiceConnected: oemEnabledSatelliteFlag is disabled");
             return;
         }
         if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
@@ -2460,7 +2640,7 @@
                         @Override
                         protected void onReceiveResult(
                                 int resultCode, Bundle resultData) {
-                            logd("onSatelliteServiceConnected.requestIsSatelliteSupported:"
+                            plogd("onSatelliteServiceConnected.requestIsSatelliteSupported:"
                                     + " resultCode=" + resultCode);
                         }
                     };
@@ -2469,7 +2649,7 @@
                 }
             }
         } else {
-            logd("onSatelliteServiceConnected: Satellite vendor service is not supported."
+            plogd("onSatelliteServiceConnected: Satellite vendor service is not supported."
                     + " Ignored the event");
         }
     }
@@ -2480,18 +2660,21 @@
      * modem. {@link SatelliteController} will then power off the satellite modem.
      */
     public void onCellularRadioPowerOffRequested() {
+        logd("onCellularRadioPowerOffRequested()");
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("onCellularRadioPowerOffRequested: oemEnabledSatelliteFlag is disabled");
+            plogd("onCellularRadioPowerOffRequested: oemEnabledSatelliteFlag is disabled");
             return;
         }
 
-        mIsRadioOn = false;
+        synchronized (mIsRadioOnLock) {
+            mIsRadioOn = false;
+        }
         requestSatelliteEnabled(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                false /* enableSatellite */, false /* enableDemoMode */,
+                false /* enableSatellite */, false /* enableDemoMode */, false /* isEmergency */,
                 new IIntegerConsumer.Stub() {
                     @Override
                     public void accept(int result) {
-                        logd("onRadioPowerOffRequested: requestSatelliteEnabled result=" + result);
+                        plogd("onRadioPowerOffRequested: requestSatelliteEnabled result=" + result);
                     }
                 });
     }
@@ -2502,7 +2685,7 @@
      */
     public boolean isSatelliteSupportedViaOem() {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("isSatelliteSupported: oemEnabledSatelliteFlag is disabled");
+            plogd("isSatelliteSupported: oemEnabledSatelliteFlag is disabled");
             return false;
         }
         Boolean supported = isSatelliteSupportedViaOemInternal();
@@ -2519,6 +2702,12 @@
             logd("getSatellitePlmnsForCarrier: carrierEnabledSatelliteFlag is disabled");
             return new ArrayList<>();
         }
+
+        if (!isSatelliteSupportedViaCarrier(subId)) {
+            logd("Satellite for carrier is not supported.");
+            return new ArrayList<>();
+        }
+
         synchronized (mSupportedSatelliteServicesLock) {
             return mMergedPlmnListPerCarrier.get(subId, new ArrayList<>()).stream().toList();
         }
@@ -2550,7 +2739,22 @@
                 loge("getSupportedSatelliteServices: mSatelliteServicesSupportedByCarriers does "
                         + "not contain key subId=" + subId);
             }
-            return new ArrayList<>();
+
+            /* Returns default capabilities when carrier config does not contain service
+               capabilities for the given plmn */
+            PersistableBundle config = getPersistableBundle(subId);
+            int [] defaultCapabilities = config.getIntArray(
+                    KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY);
+            if (defaultCapabilities == null) {
+                logd("getSupportedSatelliteServices: defaultCapabilities is null");
+                return new ArrayList<>();
+            }
+            List<Integer> capabilitiesList = Arrays.stream(
+                    defaultCapabilities).boxed().collect(Collectors.toList());
+            logd("getSupportedSatelliteServices: subId=" + subId
+                    + ", supportedServices does not contain key plmn=" + plmn
+                    + ", return default values " + capabilitiesList);
+            return capabilitiesList;
         }
     }
 
@@ -2562,13 +2766,13 @@
      */
     public boolean isSatelliteAttachRequired() {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("isSatelliteAttachRequired: oemEnabledSatelliteFlag is disabled");
+            plogd("isSatelliteAttachRequired: oemEnabledSatelliteFlag is disabled");
             return false;
         }
 
         synchronized (mSatelliteCapabilitiesLock) {
             if (mSatelliteCapabilities == null) {
-                loge("isSatelliteAttachRequired: mSatelliteCapabilities is null");
+                ploge("isSatelliteAttachRequired: mSatelliteCapabilities is null");
                 return false;
             }
             if (mSatelliteCapabilities.getSupportedRadioTechnologies().contains(
@@ -2597,6 +2801,32 @@
     }
 
     /**
+     * @return {@code true} if satellite emergency messaging is supported via carrier by any
+     * subscription on the device, {@code false} otherwise.
+     */
+    public boolean isSatelliteEmergencyMessagingSupportedViaCarrier() {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("isSatelliteEmergencyMessagingSupportedViaCarrier: carrierEnabledSatelliteFlag is"
+                    + " disabled");
+            return false;
+        }
+        for (Phone phone : PhoneFactory.getPhones()) {
+            if (isSatelliteEmergencyMessagingSupportedViaCarrier(phone.getSubId())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isSatelliteEmergencyMessagingSupportedViaCarrier(int subId) {
+        if (!isSatelliteSupportedViaCarrier(subId)) {
+            return false;
+        }
+        PersistableBundle config = getPersistableBundle(subId);
+        return config.getBoolean(KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL);
+    }
+
+    /**
      * @return {@code Pair<true, subscription ID>} if any subscription on the device is connected to
      * satellite, {@code Pair<false, null>} otherwise.
      */
@@ -2629,39 +2859,170 @@
             return true;
         }
         for (Phone phone : PhoneFactory.getPhones()) {
-            if (isSatelliteSupportedViaCarrier(phone.getSubId())) {
-                synchronized (mSatelliteConnectedLock) {
-                    Boolean isHysteresisTimeExpired =
-                            mIsSatelliteConnectedViaCarrierHysteresisTimeExpired.get(
-                                    phone.getSubId());
-                    if (isHysteresisTimeExpired != null && isHysteresisTimeExpired) {
-                        continue;
-                    }
-
-                    Long lastDisconnectedTime =
-                            mLastSatelliteDisconnectedTimesMillis.get(phone.getSubId());
-                    long satelliteConnectionHysteresisTime =
-                            getSatelliteConnectionHysteresisTimeMillis(phone.getSubId());
-                    if (lastDisconnectedTime != null
-                            && (getElapsedRealtime() - lastDisconnectedTime)
-                            <= satelliteConnectionHysteresisTime) {
-                        return true;
-                    } else {
-                        mIsSatelliteConnectedViaCarrierHysteresisTimeExpired.put(
-                                phone.getSubId(), true);
-                    }
-                }
+            if (isInSatelliteModeForCarrierRoaming(phone)) {
+                logd("isSatelliteConnectedViaCarrierWithinHysteresisTime: "
+                        + "subId:" + phone.getSubId()
+                        + " is connected to satellite within hysteresis time");
+                return true;
             }
         }
         return false;
     }
 
+    /**
+     * Get whether device is connected to satellite via carrier.
+     *
+     * @param phone phone object
+     * @return {@code true} if the device is connected to satellite using the phone within the
+     * {@link CarrierConfigManager#KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT}
+     * duration, {@code false} otherwise.
+     */
+    public boolean isInSatelliteModeForCarrierRoaming(@Nullable Phone phone) {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("isInSatelliteModeForCarrierRoaming: carrierEnabledSatelliteFlag is disabled");
+            return false;
+        }
+
+        if (phone == null) {
+            return false;
+        }
+
+        int subId = phone.getSubId();
+        if (!isSatelliteSupportedViaCarrier(subId)) {
+            return false;
+        }
+
+        ServiceState serviceState = phone.getServiceState();
+        if (serviceState == null) {
+            return false;
+        }
+
+        if (serviceState.isUsingNonTerrestrialNetwork()) {
+            return true;
+        }
+
+        if (getWwanIsInService(serviceState)) {
+            // Device is connected to terrestrial network which has coverage
+            resetCarrierRoamingSatelliteModeParams(subId);
+            return false;
+        }
+
+        synchronized (mSatelliteConnectedLock) {
+            Long lastDisconnectedTime = mLastSatelliteDisconnectedTimesMillis.get(subId);
+            long satelliteConnectionHysteresisTime =
+                    getSatelliteConnectionHysteresisTimeMillis(subId);
+            if (lastDisconnectedTime != null
+                    && (getElapsedRealtime() - lastDisconnectedTime)
+                    <= satelliteConnectionHysteresisTime) {
+                logd("isInSatelliteModeForCarrierRoaming: " + "subId:" + subId
+                        + " is connected to satellite within hysteresis time");
+                return true;
+            } else {
+                resetCarrierRoamingSatelliteModeParams(subId);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Return capabilities of carrier roaming satellite network.
+     *
+     * @param phone phone object
+     * @return The list of services supported by the carrier associated with the {@code subId}
+     */
+    @NonNull
+    public List<Integer> getCapabilitiesForCarrierRoamingSatelliteMode(Phone phone) {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("getCapabilitiesForCarrierRoamingSatelliteMode: carrierEnabledSatelliteFlag"
+                    + " is disabled");
+            return new ArrayList<>();
+        }
+
+        synchronized (mSatelliteConnectedLock) {
+            int subId = phone.getSubId();
+            if (mSatModeCapabilitiesForCarrierRoaming.containsKey(subId)) {
+                return mSatModeCapabilitiesForCarrierRoaming.get(subId);
+            }
+        }
+
+        return new ArrayList<>();
+    }
+
+    /**
+     * Request to get the {@link SatelliteSessionStats} of the satellite service.
+     *
+     * @param subId The subId of the subscription to the satellite session stats for.
+     * @param result The result receiver that returns the {@link SatelliteSessionStats}
+     *               if the request is successful or an error code if the request failed.
+     */
+    public void requestSatelliteSessionStats(int subId, @NonNull ResultReceiver result) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            return;
+        }
+        mSessionMetricsStats.requestSatelliteSessionStats(subId, result);
+    }
+
+    /**
+     * Get the carrier-enabled emergency call wait for connection timeout millis
+     */
+    public long getCarrierEmergencyCallWaitForConnectionTimeoutMillis() {
+        long maxTimeoutMillis = 0;
+        for (Phone phone : PhoneFactory.getPhones()) {
+            if (!isSatelliteEmergencyMessagingSupportedViaCarrier(phone.getSubId())) {
+                continue;
+            }
+
+            int timeoutMillis =
+                    getCarrierEmergencyCallWaitForConnectionTimeoutMillis(phone.getSubId());
+            // Prioritize getting the timeout duration from the phone that is in satellite mode
+            // with carrier roaming
+            if (isInSatelliteModeForCarrierRoaming(phone)) {
+                return timeoutMillis;
+            }
+            if (maxTimeoutMillis < timeoutMillis) {
+                maxTimeoutMillis = timeoutMillis;
+            }
+        }
+        if (maxTimeoutMillis != 0) {
+            return maxTimeoutMillis;
+        }
+        return DEFAULT_CARRIER_EMERGENCY_CALL_WAIT_FOR_CONNECTION_TIMEOUT_MILLIS;
+    }
+
+    private int getCarrierEmergencyCallWaitForConnectionTimeoutMillis(int subId) {
+        PersistableBundle config = getPersistableBundle(subId);
+        return config.getInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT);
+    }
+
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected long getElapsedRealtime() {
         return SystemClock.elapsedRealtime();
     }
 
     /**
+     * Register the handler for SIM Refresh notifications.
+     * @param handler Handler for notification message.
+     * @param what User-defined message code.
+     */
+    public void registerIccRefresh(Handler handler, int what) {
+        for (Phone phone : PhoneFactory.getPhones()) {
+            CommandsInterface ci = phone.mCi;
+            ci.registerForIccRefresh(handler, what, null);
+        }
+    }
+
+    /**
+     * Unregister the handler for SIM Refresh notifications.
+     * @param handler Handler for notification message.
+     */
+    public void unRegisterIccRefresh(Handler handler) {
+        for (Phone phone : PhoneFactory.getPhones()) {
+            CommandsInterface ci = phone.mCi;
+            ci.unregisterForIccRefresh(handler);
+        }
+    }
+
+    /**
      * To use the satellite service, update the EntitlementStatus and the PlmnAllowedList after
      * receiving the satellite configuration from the entitlement server. If satellite
      * entitlement is enabled, enable satellite for the carrier. Otherwise, disable satellite.
@@ -2669,11 +3030,14 @@
      * @param subId              subId
      * @param entitlementEnabled {@code true} Satellite service enabled
      * @param allowedPlmnList    plmn allowed list to use the satellite service
+     * @param barredPlmnList    plmn barred list to pass the modem
      * @param callback           callback for accept
      */
     public void onSatelliteEntitlementStatusUpdated(int subId, boolean entitlementEnabled,
-            List<String> allowedPlmnList, @Nullable IIntegerConsumer callback) {
+            @Nullable List<String> allowedPlmnList, @Nullable List<String> barredPlmnList,
+            @Nullable IIntegerConsumer callback) {
         if (!mFeatureFlags.carrierEnabledSatelliteFlag()) {
+            logd("onSatelliteEntitlementStatusUpdated: carrierEnabledSatelliteFlag is not enabled");
             return;
         }
 
@@ -2685,9 +3049,16 @@
                 }
             };
         }
-        logd("onSatelliteEntitlementStatusUpdated subId=" + subId + " , entitlementEnabled="
-                + entitlementEnabled + ", allowedPlmnList=" + (Objects.equals(null, allowedPlmnList)
-                ? "" : allowedPlmnList + ""));
+        if (allowedPlmnList == null) {
+            allowedPlmnList = new ArrayList<>();
+        }
+        if (barredPlmnList == null) {
+            barredPlmnList = new ArrayList<>();
+        }
+        logd("onSatelliteEntitlementStatusUpdated subId=" + subId + ", entitlementEnabled="
+                + entitlementEnabled + ", allowedPlmnList=["
+                + String.join(",", allowedPlmnList) + "]" + ", barredPlmnList=["
+                + String.join(",", barredPlmnList) + "]");
 
         synchronized (mSupportedSatelliteServicesLock) {
             if (mSatelliteEntitlementStatusPerCarrier.get(subId, false) != entitlementEnabled) {
@@ -2700,12 +3071,18 @@
                     loge("onSatelliteEntitlementStatusUpdated: setSubscriptionProperty, e=" + e);
                 }
             }
-            mMergedPlmnListPerCarrier.remove(subId);
 
-            mEntitlementPlmnListPerCarrier.put(subId, allowedPlmnList);
-            updatePlmnListPerCarrier(subId);
-            configureSatellitePlmnForCarrier(subId);
-            mSubscriptionManagerService.setSatelliteEntitlementPlmnList(subId, allowedPlmnList);
+            if (isValidPlmnList(allowedPlmnList) && isValidPlmnList(barredPlmnList)) {
+                mMergedPlmnListPerCarrier.remove(subId);
+                mEntitlementPlmnListPerCarrier.put(subId, allowedPlmnList);
+                mEntitlementBarredPlmnListPerCarrier.put(subId, barredPlmnList);
+                updatePlmnListPerCarrier(subId);
+                configureSatellitePlmnForCarrier(subId);
+                mSubscriptionManagerService.setSatelliteEntitlementPlmnList(subId, allowedPlmnList);
+            } else {
+                loge("onSatelliteEntitlementStatusUpdated: either invalid allowedPlmnList "
+                        + "or invalid barredPlmnList");
+            }
 
             if (mSatelliteEntitlementStatusPerCarrier.get(subId, false)) {
                 removeAttachRestrictionForCarrier(subId,
@@ -2718,6 +3095,20 @@
     }
 
     /**
+     * A list of PLMNs is considered valid if either the list is empty or all PLMNs in the list
+     * are valid.
+     */
+    private boolean isValidPlmnList(@NonNull List<String> plmnList) {
+        for (String plmn : plmnList) {
+            if (!TelephonyUtils.isValidPlmn(plmn)) {
+                ploge("Invalid PLMN = " + plmn);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * If we have not successfully queried the satellite modem for its satellite service support,
      * we will retry the query one more time. Otherwise, we will return the cached result.
      */
@@ -2736,7 +3127,7 @@
                 new ResultReceiver(this) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        logd("isSatelliteSupportedViaOemInternal.requestIsSatelliteSupported:"
+                        plogd("isSatelliteSupportedViaOemInternal.requestIsSatelliteSupported:"
                                 + " resultCode=" + resultCode);
                     }
                 });
@@ -2746,12 +3137,12 @@
     private void handleEventProvisionSatelliteServiceDone(
             @NonNull ProvisionSatelliteServiceArgument arg,
             @SatelliteManager.SatelliteResult int result) {
-        logd("handleEventProvisionSatelliteServiceDone: result="
+        plogd("handleEventProvisionSatelliteServiceDone: result="
                 + result + ", subId=" + arg.subId);
 
         Consumer<Integer> callback = mSatelliteProvisionCallbacks.remove(arg.subId);
         if (callback == null) {
-            loge("handleEventProvisionSatelliteServiceDone: callback is null for subId="
+            ploge("handleEventProvisionSatelliteServiceDone: callback is null for subId="
                     + arg.subId);
             mProvisionMetricsStats
                     .setResultCode(SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE)
@@ -2772,16 +3163,20 @@
         } else {
             callback.accept(result);
         }
+        mProvisionMetricsStats.setResultCode(result)
+                .setIsProvisionRequest(true)
+                .reportProvisionMetrics();
+        mControllerMetricsStats.reportProvisionCount(result);
     }
 
     private void handleEventDeprovisionSatelliteServiceDone(
             @NonNull ProvisionSatelliteServiceArgument arg,
             @SatelliteManager.SatelliteResult int result) {
         if (arg == null) {
-            loge("handleEventDeprovisionSatelliteServiceDone: arg is null");
+            ploge("handleEventDeprovisionSatelliteServiceDone: arg is null");
             return;
         }
-        logd("handleEventDeprovisionSatelliteServiceDone: result="
+        plogd("handleEventDeprovisionSatelliteServiceDone: result="
                 + result + ", subId=" + arg.subId);
 
         if (result == SATELLITE_RESULT_SUCCESS
@@ -2889,7 +3284,7 @@
                 new ResultReceiver(this) {
                     @Override
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        logd("isSatelliteViaOemProvisioned: resultCode=" + resultCode);
+                        plogd("isSatelliteViaOemProvisioned: resultCode=" + resultCode);
                     }
                 });
         return null;
@@ -2898,6 +3293,13 @@
     private void handleSatelliteEnabled(SatelliteControllerHandlerRequest request) {
         RequestSatelliteEnabledArgument argument =
                 (RequestSatelliteEnabledArgument) request.argument;
+        handlePersistentLoggingOnSessionStart(argument);
+        if (mSatelliteSessionController != null) {
+            mSatelliteSessionController.onSatelliteEnablementStarted(argument.enableSatellite);
+        } else {
+            ploge("handleSatelliteEnabled: mSatelliteSessionController is not initialized yet");
+        }
+
         if (!argument.enableSatellite && mSatelliteModemInterface.isSatelliteServiceSupported()) {
             synchronized (mIsSatelliteEnabledLock) {
                 mWaitingForDisableSatelliteModemResponse = true;
@@ -2907,8 +3309,13 @@
 
         Message onCompleted = obtainMessage(EVENT_SET_SATELLITE_ENABLED_DONE, request);
         mSatelliteModemInterface.requestSatelliteEnabled(argument.enableSatellite,
-                argument.enableDemoMode, onCompleted);
+                argument.enableDemoMode, argument.isEmergency, onCompleted);
         startWaitForSatelliteEnablingResponseTimer(argument);
+        // Logs satellite session timestamps for session metrics
+        if (argument.enableSatellite) {
+            mSessionStartTimeStamp = System.currentTimeMillis();
+        }
+        mSessionProcessingTimeStamp = System.currentTimeMillis();
     }
 
     private void handleRequestSatelliteAttachRestrictionForCarrierCmd(
@@ -2931,7 +3338,10 @@
             mIsSatelliteSupported = supported;
         }
         mSatelliteSessionController = SatelliteSessionController.make(
-                mContext, getLooper(), supported);
+                mContext, getLooper(), mFeatureFlags, supported);
+        plogd("create a new SatelliteSessionController due to isSatelliteSupported state has "
+                + "changed to " + supported);
+
         if (supported) {
             registerForSatelliteProvisionStateChanged();
             registerForPendingDatagramCount();
@@ -2943,14 +3353,14 @@
                     new ResultReceiver(this) {
                         @Override
                         protected void onReceiveResult(int resultCode, Bundle resultData) {
-                            logd("requestIsSatelliteProvisioned: resultCode=" + resultCode
+                            plogd("requestIsSatelliteProvisioned: resultCode=" + resultCode
                                     + ", resultData=" + resultData);
                             requestSatelliteEnabled(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
-                                    false, false,
+                                    false, false, false,
                                     new IIntegerConsumer.Stub() {
                                         @Override
                                         public void accept(int result) {
-                                            logd("requestSatelliteEnabled: result=" + result);
+                                            plogd("requestSatelliteEnabled: result=" + result);
                                         }
                                     });
                         }
@@ -2959,11 +3369,12 @@
                     new ResultReceiver(this) {
                         @Override
                         protected void onReceiveResult(int resultCode, Bundle resultData) {
-                            logd("requestSatelliteCapabilities: resultCode=" + resultCode
+                            plogd("requestSatelliteCapabilities: resultCode=" + resultCode
                                     + ", resultData=" + resultData);
                         }
                     });
         }
+        registerForSatelliteSupportedStateChanged();
     }
 
     private void updateSatelliteEnabledState(boolean enabled, String caller) {
@@ -2974,7 +3385,10 @@
             mSatelliteSessionController.onSatelliteEnabledStateChanged(enabled);
             mSatelliteSessionController.setDemoMode(mIsDemoModeEnabled);
         } else {
-            loge(caller + ": mSatelliteSessionController is not initialized yet");
+            ploge(caller + ": mSatelliteSessionController is not initialized yet");
+        }
+        if (!enabled) {
+            mIsModemEnabledReportingNtnSignalStrength.set(false);
         }
     }
 
@@ -3010,7 +3424,7 @@
 
     private void registerForNtnSignalStrengthChanged() {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("registerForNtnSignalStrengthChanged: oemEnabledSatelliteFlag is disabled");
+            plogd("registerForNtnSignalStrengthChanged: oemEnabledSatelliteFlag is disabled");
             return;
         }
 
@@ -3025,7 +3439,7 @@
 
     private void registerForCapabilitiesChanged() {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("registerForCapabilitiesChanged: oemEnabledSatelliteFlag is disabled");
+            plogd("registerForCapabilitiesChanged: oemEnabledSatelliteFlag is disabled");
             return;
         }
 
@@ -3038,8 +3452,18 @@
         }
     }
 
+    private void registerForSatelliteSupportedStateChanged() {
+        if (mSatelliteModemInterface.isSatelliteServiceSupported()) {
+            if (!mRegisteredForSatelliteSupportedStateChanged.get()) {
+                mSatelliteModemInterface.registerForSatelliteSupportedStateChanged(
+                        this, EVENT_SATELLITE_SUPPORTED_STATE_CHANGED, null);
+                mRegisteredForSatelliteSupportedStateChanged.set(true);
+            }
+        }
+    }
+
     private void handleEventSatelliteProvisionStateChanged(boolean provisioned) {
-        logd("handleSatelliteProvisionStateChangedEvent: provisioned=" + provisioned);
+        plogd("handleSatelliteProvisionStateChangedEvent: provisioned=" + provisioned);
 
         synchronized (mSatelliteViaOemProvisionLock) {
             persistOemEnabledSatelliteProvisionStatus(provisioned);
@@ -3051,7 +3475,7 @@
             try {
                 listener.onSatelliteProvisionStateChanged(provisioned);
             } catch (RemoteException e) {
-                logd("handleSatelliteProvisionStateChangedEvent RemoteException: " + e);
+                plogd("handleSatelliteProvisionStateChangedEvent RemoteException: " + e);
                 deadCallersList.add(listener);
             }
         });
@@ -3062,7 +3486,7 @@
 
     private void handleEventSatelliteModemStateChanged(
             @SatelliteManager.SatelliteModemState int state) {
-        logd("handleEventSatelliteModemStateChanged: state=" + state);
+        plogd("handleEventSatelliteModemStateChanged: state=" + state);
         if (state == SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE
                 || state == SatelliteManager.SATELLITE_MODEM_STATE_OFF) {
             synchronized (mIsSatelliteEnabledLock) {
@@ -3080,8 +3504,8 @@
                     }
                     moveSatelliteToOffStateAndCleanUpResources(error, callback);
                 } else {
-                    logd("Either waiting for the response of disabling satellite modem or the event"
-                            + " should be ignored because isSatelliteEnabled="
+                    plogd("Either waiting for the response of disabling satellite modem or the"
+                            + " event should be ignored because isSatelliteEnabled="
                             + isSatelliteEnabled()
                             + ", mIsSatelliteEnabled=" + mIsSatelliteEnabled);
                 }
@@ -3091,7 +3515,7 @@
             if (mSatelliteSessionController != null) {
                 mSatelliteSessionController.onSatelliteModemStateChanged(state);
             } else {
-                loge("handleEventSatelliteModemStateChanged: mSatelliteSessionController is null");
+                ploge("handleEventSatelliteModemStateChanged: mSatelliteSessionController is null");
             }
         }
     }
@@ -3106,13 +3530,14 @@
         synchronized (mNtnSignalsStrengthLock) {
             mNtnSignalStrength = ntnSignalStrength;
         }
+        mSessionMetricsStats.updateMaxNtnSignalStrengthLevel(ntnSignalStrength.getLevel());
 
         List<INtnSignalStrengthCallback> deadCallersList = new ArrayList<>();
         mNtnSignalStrengthChangedListeners.values().forEach(listener -> {
             try {
                 listener.onNtnSignalStrengthChanged(ntnSignalStrength);
             } catch (RemoteException e) {
-                logd("handleEventNtnSignalStrengthChanged RemoteException: " + e);
+                plogd("handleEventNtnSignalStrengthChanged RemoteException: " + e);
                 deadCallersList.add(listener);
             }
         });
@@ -3122,9 +3547,9 @@
     }
 
     private void handleEventSatelliteCapabilitiesChanged(SatelliteCapabilities capabilities) {
-        logd("handleEventSatelliteCapabilitiesChanged()");
+        plogd("handleEventSatelliteCapabilitiesChanged()");
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("handleEventSatelliteCapabilitiesChanged: oemEnabledSatelliteFlag is disabled");
+            plogd("handleEventSatelliteCapabilitiesChanged: oemEnabledSatelliteFlag is disabled");
             return;
         }
 
@@ -3137,7 +3562,7 @@
             try {
                 listener.onSatelliteCapabilitiesChanged(capabilities);
             } catch (RemoteException e) {
-                logd("handleEventSatelliteCapabilitiesChanged RemoteException: " + e);
+                plogd("handleEventSatelliteCapabilitiesChanged RemoteException: " + e);
                 deadCallersList.add(listener);
             }
         });
@@ -3146,13 +3571,167 @@
         });
     }
 
+    private void handleEventSatelliteSupportedStateChanged(boolean supported) {
+        plogd("handleSatelliteSupportedStateChangedEvent: supported=" + supported);
+
+        synchronized (mIsSatelliteSupportedLock) {
+            if (mIsSatelliteSupported != null && mIsSatelliteSupported == supported) {
+                if (DBG) {
+                    plogd("current satellite support state and new supported state are matched,"
+                            + " ignore update.");
+                }
+                return;
+            }
+
+            updateSatelliteSupportedStateWhenSatelliteServiceConnected(supported);
+
+            /* In case satellite has been reported as not support from modem, but satellite is
+               enabled, request disable satellite. */
+            synchronized (mIsSatelliteEnabledLock) {
+                if (!supported && mIsSatelliteEnabled != null && mIsSatelliteEnabled) {
+                    plogd("Invoke requestSatelliteEnabled(), supported=false, "
+                            + "mIsSatelliteEnabled=true");
+                    requestSatelliteEnabled(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                            false /* enableSatellite */, false /* enableDemoMode */,
+                            false /* isEmergency */,
+                            new IIntegerConsumer.Stub() {
+                                @Override
+                                public void accept(int result) {
+                                    plogd("handleSatelliteSupportedStateChangedEvent: request "
+                                            + "satellite disable, result="
+                                            + result);
+                                }
+                            });
+
+                }
+            }
+            mIsSatelliteSupported = supported;
+        }
+
+        List<ISatelliteSupportedStateCallback> deadCallersList = new ArrayList<>();
+        mSatelliteSupportedStateChangedListeners.values().forEach(listener -> {
+            try {
+                listener.onSatelliteSupportedStateChanged(supported);
+            } catch (RemoteException e) {
+                plogd("handleSatelliteSupportedStateChangedEvent RemoteException: " + e);
+                deadCallersList.add(listener);
+            }
+        });
+        deadCallersList.forEach(listener -> {
+            mSatelliteSupportedStateChangedListeners.remove(listener.asBinder());
+        });
+    }
+
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected void setSettingsKeyForSatelliteMode(int val) {
-        logd("setSettingsKeyForSatelliteMode val: " + val);
+        plogd("setSettingsKeyForSatelliteMode val: " + val);
         Settings.Global.putInt(mContext.getContentResolver(),
                     Settings.Global.SATELLITE_MODE_ENABLED, val);
     }
 
+    /**
+     * Allow screen rotation temporary in rotation locked foldable device.
+     * <p>
+     * Temporarily allow screen rotation user to catch satellite signals properly by UI guide in
+     * emergency situations. Unlock the setting value so that the screen rotation is not locked, and
+     * return it to the original value when the satellite service is finished.
+     * <p>
+     * Note that, only the unfolded screen will be temporarily allowed screen rotation.
+     *
+     * @param val {@link SATELLITE_MODE_ENABLED_TRUE} if satellite mode is enabled,
+     *     {@link SATELLITE_MODE_ENABLED_FALSE} satellite mode is not enabled.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    protected void setSettingsKeyToAllowDeviceRotation(int val) {
+        // Only allows on a foldable device type.
+        if (!isFoldable(mContext)) {
+            return;
+        }
+
+        switch (val) {
+            case SATELLITE_MODE_ENABLED_TRUE:
+                mDeviceRotationLockToBackupAndRestore =
+                        Settings.Secure.getString(mContentResolver,
+                                Settings.Secure.DEVICE_STATE_ROTATION_LOCK);
+                String unlockedRotationSettings = replaceDeviceRotationValue(
+                        mDeviceRotationLockToBackupAndRestore == null
+                                ? "" : mDeviceRotationLockToBackupAndRestore,
+                        Settings.Secure.DEVICE_STATE_ROTATION_KEY_UNFOLDED,
+                        Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
+                Settings.Secure.putString(mContentResolver,
+                        Settings.Secure.DEVICE_STATE_ROTATION_LOCK, unlockedRotationSettings);
+                logd("setSettingsKeyToAllowDeviceRotation(TRUE), RotationSettings is changed"
+                        + " from " + mDeviceRotationLockToBackupAndRestore
+                        + " to " + unlockedRotationSettings);
+                break;
+            case SATELLITE_MODE_ENABLED_FALSE:
+                if (mDeviceRotationLockToBackupAndRestore == null) {
+                    break;
+                }
+                Settings.Secure.putString(mContentResolver,
+                        Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+                        mDeviceRotationLockToBackupAndRestore);
+                logd("setSettingsKeyToAllowDeviceRotation(FALSE), RotationSettings is restored to"
+                        + mDeviceRotationLockToBackupAndRestore);
+                mDeviceRotationLockToBackupAndRestore = "";
+                break;
+            default:
+                loge("setSettingsKeyToAllowDeviceRotation(" + val + "), never reach here.");
+                break;
+        }
+    }
+
+    /**
+     * If the device type is foldable.
+     *
+     * @param context context
+     * @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;
+    }
+
+    /**
+     * Replaces a value of given a target key with a new value in a string of key-value pairs.
+     * <p>
+     * Replaces the value corresponding to the target key with a new value. If the key value is not
+     * found in the device rotation information, it is not replaced.
+     *
+     * @param deviceRotationValue Device rotation key values separated by colon(':').
+     * @param targetKey The key of the new item caller wants to add.
+     * @param newValue  The value of the new item caller want to add.
+     * @return A new string where all the key-value pairs.
+     */
+    private static String replaceDeviceRotationValue(
+            @NonNull String deviceRotationValue, int targetKey, int newValue) {
+        // Use list of Key-Value pair
+        List<Pair<Integer, Integer>> keyValuePairs = new ArrayList<>();
+
+        String[] pairs = deviceRotationValue.split(":");
+        if (pairs.length % 2 != 0) {
+            // Return without modifying. The key-value may be incorrect if length is an odd number.
+            loge("The length of key-value pair do not match. Return without modification.");
+            return deviceRotationValue;
+        }
+
+        // collect into keyValuePairs
+        for (int i = 0; i < pairs.length; i += 2) {
+            try {
+                int key = Integer.parseInt(pairs[i]);
+                int value = Integer.parseInt(pairs[i + 1]);
+                keyValuePairs.add(new Pair<>(key, key == targetKey ? newValue : value));
+            } catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
+                // Return without modifying if got exception.
+                loge("got error while parsing key-value. Return without modification. e:" + e);
+                return deviceRotationValue;
+            }
+        }
+
+        return keyValuePairs.stream()
+                .map(pair -> pair.first + ":" + pair.second) // Convert to "key:value" format
+                .collect(Collectors.joining(":")); // Join pairs with colons
+    }
+
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected boolean areAllRadiosDisabled() {
         synchronized (mRadioStateLock) {
@@ -3160,21 +3739,22 @@
                     || (mDisableNFCOnSatelliteEnabled && mNfcStateEnabled)
                     || (mDisableWifiOnSatelliteEnabled && mWifiStateEnabled)
                     || (mDisableUWBOnSatelliteEnabled && mUwbStateEnabled)) {
-                logd("All radios are not disabled yet.");
+                plogd("All radios are not disabled yet.");
                 return false;
             }
-            logd("All radios are disabled.");
+            plogd("All radios are disabled.");
             return true;
         }
     }
 
     private void evaluateToSendSatelliteEnabledSuccess() {
-        logd("evaluateToSendSatelliteEnabledSuccess");
+        plogd("evaluateToSendSatelliteEnabledSuccess");
         synchronized (mSatelliteEnabledRequestLock) {
             if (areAllRadiosDisabled() && (mSatelliteEnabledRequest != null)
                     && mWaitingForRadioDisabled) {
-                logd("Sending success to callback that sent enable satellite request");
+                plogd("Sending success to callback that sent enable satellite request");
                 setDemoModeEnabled(mSatelliteEnabledRequest.enableDemoMode);
+                mIsEmergency = mSatelliteEnabledRequest.isEmergency;
                 synchronized (mIsSatelliteEnabledLock) {
                     mIsSatelliteEnabled = mSatelliteEnabledRequest.enableSatellite;
                 }
@@ -3189,7 +3769,7 @@
     }
 
     private void resetSatelliteEnabledRequest() {
-        logd("resetSatelliteEnabledRequest");
+        plogd("resetSatelliteEnabledRequest");
         synchronized (mSatelliteEnabledRequestLock) {
             mSatelliteEnabledRequest = null;
             mWaitingForRadioDisabled = false;
@@ -3198,12 +3778,15 @@
 
     private void moveSatelliteToOffStateAndCleanUpResources(
             @SatelliteManager.SatelliteResult int error, @Nullable Consumer<Integer> callback) {
-        logd("moveSatelliteToOffStateAndCleanUpResources");
+        plogd("moveSatelliteToOffStateAndCleanUpResources");
         synchronized (mIsSatelliteEnabledLock) {
             resetSatelliteEnabledRequest();
             setDemoModeEnabled(false);
+            handlePersistentLoggingOnSessionEnd(mIsEmergency);
+            mIsEmergency = false;
             mIsSatelliteEnabled = false;
             setSettingsKeyForSatelliteMode(SATELLITE_MODE_ENABLED_FALSE);
+            setSettingsKeyToAllowDeviceRotation(SATELLITE_MODE_ENABLED_FALSE);
             if (callback != null) callback.accept(error);
             updateSatelliteEnabledState(
                     false, "moveSatelliteToOffStateAndCleanUpResources");
@@ -3213,6 +3796,7 @@
     private void setDemoModeEnabled(boolean enabled) {
         mIsDemoModeEnabled = enabled;
         mDatagramController.setDemoMode(mIsDemoModeEnabled);
+        plogd("setDemoModeEnabled: mIsDemoModeEnabled=" + mIsDemoModeEnabled);
     }
 
     private boolean isMockModemAllowed() {
@@ -3228,10 +3812,12 @@
         synchronized (mSupportedSatelliteServicesLock) {
             List<String> carrierPlmnList = mMergedPlmnListPerCarrier.get(subId,
                     new ArrayList<>()).stream().toList();
+            List<String> barredPlmnList = mEntitlementBarredPlmnListPerCarrier.get(subId,
+                    new ArrayList<>()).stream().toList();
             int slotId = SubscriptionManager.getSlotIndex(subId);
             mSatelliteModemInterface.setSatellitePlmn(slotId, carrierPlmnList,
                     SatelliteServiceUtils.mergeStrLists(
-                            carrierPlmnList, mSatellitePlmnListFromOverlayConfig),
+                            carrierPlmnList, mSatellitePlmnListFromOverlayConfig, barredPlmnList),
                     obtainMessage(EVENT_SET_SATELLITE_PLMN_INFO_DONE));
         }
     }
@@ -3268,14 +3854,23 @@
      * Otherwise, If the carrierPlmnList exist then used it.
      */
     private void updatePlmnListPerCarrier(int subId) {
+        plogd("updatePlmnListPerCarrier: subId=" + subId);
         synchronized (mSupportedSatelliteServicesLock) {
             List<String> carrierPlmnList, entitlementPlmnList;
-            entitlementPlmnList = mEntitlementPlmnListPerCarrier.get(subId,
-                    new ArrayList<>()).stream().toList();
-            if (!entitlementPlmnList.isEmpty()) {
-                mMergedPlmnListPerCarrier.put(subId, entitlementPlmnList);
-                logd("update it using entitlementPlmnList=" + entitlementPlmnList);
-                return;
+            if (getConfigForSubId(subId).getBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL,
+                    false)) {
+                entitlementPlmnList = mEntitlementPlmnListPerCarrier.get(subId,
+                        new ArrayList<>()).stream().toList();
+                plogd("updatePlmnListPerCarrier: entitlementPlmnList="
+                        + String.join(",", entitlementPlmnList)
+                        + " size=" + entitlementPlmnList.size());
+                if (!entitlementPlmnList.isEmpty()) {
+                    mMergedPlmnListPerCarrier.put(subId, entitlementPlmnList);
+                    plogd("mMergedPlmnListPerCarrier is updated by Entitlement");
+                    mCarrierRoamingSatelliteControllerStats.reportConfigDataSource(
+                            SatelliteConstants.CONFIG_DATA_SOURCE_ENTITLEMENT);
+                    return;
+                }
             }
 
             SatelliteConfig satelliteConfig = getSatelliteConfig();
@@ -3284,26 +3879,33 @@
                 int carrierId = tm.createForSubscriptionId(subId).getSimCarrierId();
                 List<String> plmnList = satelliteConfig.getAllSatellitePlmnsForCarrier(carrierId);
                 if (!plmnList.isEmpty()) {
-                    logd("mMergedPlmnListPerCarrier is updated by ConfigUpdater : " + plmnList);
+                    plogd("mMergedPlmnListPerCarrier is updated by ConfigUpdater : "
+                            + String.join(",", plmnList));
                     mMergedPlmnListPerCarrier.put(subId, plmnList);
+                    mCarrierRoamingSatelliteControllerStats.reportConfigDataSource(
+                            SatelliteConstants.CONFIG_DATA_SOURCE_CONFIG_UPDATER);
                     return;
                 }
             }
 
-            if (mSatelliteServicesSupportedByCarriers.containsKey(subId)) {
+            if (mSatelliteServicesSupportedByCarriers.containsKey(subId)
+                    && mSatelliteServicesSupportedByCarriers.get(subId) != null) {
                 carrierPlmnList =
                         mSatelliteServicesSupportedByCarriers.get(subId).keySet().stream().toList();
-                logd("mMergedPlmnListPerCarrier is updated by carrier config");
+                plogd("mMergedPlmnListPerCarrier is updated by carrier config: "
+                        + String.join(",", carrierPlmnList));
+                mCarrierRoamingSatelliteControllerStats.reportConfigDataSource(
+                        SatelliteConstants.CONFIG_DATA_SOURCE_CARRIER_CONFIG);
             } else {
                 carrierPlmnList = new ArrayList<>();
+                plogd("Empty mMergedPlmnListPerCarrier");
             }
             mMergedPlmnListPerCarrier.put(subId, carrierPlmnList);
-            logd("update it using carrierPlmnList=" + carrierPlmnList);
         }
     }
 
     private void updateSupportedSatelliteServices(int subId) {
-        logd("updateSupportedSatelliteServices with subId " + subId);
+        plogd("updateSupportedSatelliteServices with subId " + subId);
         synchronized (mSupportedSatelliteServicesLock) {
             SatelliteConfig satelliteConfig = getSatelliteConfig();
 
@@ -3315,19 +3917,19 @@
                         satelliteConfig.getSupportedSatelliteServices(carrierId);
                 if (!supportedServicesPerPlmn.isEmpty()) {
                     mSatelliteServicesSupportedByCarriers.put(subId, supportedServicesPerPlmn);
-                    logd("updateSupportedSatelliteServices using ConfigUpdater, "
-                            + "supportedServicesPerPlmn = " + supportedServicesPerPlmn);
+                    plogd("updateSupportedSatelliteServices using ConfigUpdater, "
+                            + "supportedServicesPerPlmn = " + supportedServicesPerPlmn.size());
                     updatePlmnListPerCarrier(subId);
                     return;
                 } else {
-                    logd("supportedServicesPerPlmn is empty");
+                    plogd("supportedServicesPerPlmn is empty");
                 }
             }
 
             mSatelliteServicesSupportedByCarriers.put(
                     subId, readSupportedSatelliteServicesFromCarrierConfig(subId));
             updatePlmnListPerCarrier(subId);
-            logd("updateSupportedSatelliteServices using carrier config");
+            plogd("updateSupportedSatelliteServices using carrier config");
         }
     }
 
@@ -3345,16 +3947,10 @@
 
     @NonNull
     private Map<String, Set<Integer>> readSupportedSatelliteServicesFromCarrierConfig(int subId) {
-        synchronized (mCarrierConfigArrayLock) {
-            PersistableBundle config = mCarrierConfigArray.get(subId);
-            if (config == null) {
-                config = getConfigForSubId(subId);
-                mCarrierConfigArray.put(subId, config);
-            }
-            return SatelliteServiceUtils.parseSupportedSatelliteServices(
-                    config.getPersistableBundle(
-                            KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE));
-        }
+        PersistableBundle config = getPersistableBundle(subId);
+        return SatelliteServiceUtils.parseSupportedSatelliteServices(
+                config.getPersistableBundle(
+                        KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE));
     }
 
     @NonNull private PersistableBundle getConfigForSubId(int subId) {
@@ -3362,7 +3958,10 @@
                 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_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);
         if (config == null || config.isEmpty()) {
             config = CarrierConfigManager.getDefaultConfig();
         }
@@ -3371,7 +3970,7 @@
 
     private void handleCarrierConfigChanged(int slotIndex, int subId, int carrierId,
             int specificCarrierId) {
-        logd("handleCarrierConfigChanged(): slotIndex(" + slotIndex + "), subId("
+        plogd("handleCarrierConfigChanged(): slotIndex(" + slotIndex + "), subId("
                 + subId + "), carrierId(" + carrierId + "), specificCarrierId("
                 + specificCarrierId + ")");
         if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
@@ -3382,15 +3981,11 @@
         updateEntitlementPlmnListPerCarrier(subId);
         updateSupportedSatelliteServicesForActiveSubscriptions();
         processNewCarrierConfigData(subId);
+        resetCarrierRoamingSatelliteModeParams(subId);
     }
 
     private void processNewCarrierConfigData(int subId) {
         configureSatellitePlmnForCarrier(subId);
-        synchronized (mIsSatelliteEnabledLock) {
-            mSatelliteAttachRestrictionForCarrierArray.clear();
-            mIsSatelliteAttachEnabledForCarrierArrayPerSub.clear();
-        }
-
         setSatelliteAttachEnabledForCarrierOnSimLoaded(subId);
         updateRestrictReasonForEntitlementPerCarrier(subId);
     }
@@ -3402,26 +3997,28 @@
         }
     }
 
-    /** If there is no cached entitlement plmn list, read it from the db and use it if it is not an
-     * empty list. */
+    /**
+     * If there is no cached entitlement plmn list, read it from the db and use it if it is not an
+     * empty list.
+     */
     private void updateEntitlementPlmnListPerCarrier(int subId) {
         if (!getConfigForSubId(subId).getBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false)) {
-            logd("don't support entitlement");
+            plogd("don't support entitlement");
             return;
         }
 
         synchronized (mSupportedSatelliteServicesLock) {
             if (mEntitlementPlmnListPerCarrier.indexOfKey(subId) < 0) {
-                logd("updateEntitlementPlmnListPerCarrier: no correspondent cache, load from "
+                plogd("updateEntitlementPlmnListPerCarrier: no correspondent cache, load from "
                         + "persist storage");
                 List<String> entitlementPlmnList =
                         mSubscriptionManagerService.getSatelliteEntitlementPlmnList(subId);
                 if (entitlementPlmnList.isEmpty()) {
-                    loge("updateEntitlementPlmnListPerCarrier: no data for subId(" + subId + ")");
+                    plogd("updateEntitlementPlmnListPerCarrier: read empty list");
                     return;
                 }
-                logd("updateEntitlementPlmnListPerCarrier: entitlementPlmnList="
-                        + entitlementPlmnList);
+                plogd("updateEntitlementPlmnListPerCarrier: entitlementPlmnList="
+                        + String.join(",", entitlementPlmnList));
                 mEntitlementPlmnListPerCarrier.put(subId, entitlementPlmnList);
             }
         }
@@ -3450,7 +4047,7 @@
         try {
             strArray = mContext.getResources().getStringArray(id);
         } catch (Resources.NotFoundException ex) {
-            loge("readStringArrayFromOverlayConfig: id= " + id + ", ex=" + ex);
+            ploge("readStringArrayFromOverlayConfig: id= " + id + ", ex=" + ex);
         }
         if (strArray == null) {
             strArray = new String[0];
@@ -3480,7 +4077,7 @@
                 return !cachedRestrictionSet.contains(
                         SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER);
             } else {
-                logd("isSatelliteAttachEnabledForCarrierByUser() no correspondent cache, "
+                plogd("isSatelliteAttachEnabledForCarrierByUser() no correspondent cache, "
                         + "load from persist storage");
                 try {
                     String enabled =
@@ -3489,13 +4086,13 @@
                                     mContext.getOpPackageName(), mContext.getAttributionTag());
 
                     if (enabled == null) {
-                        loge("isSatelliteAttachEnabledForCarrierByUser: invalid subId, subId="
+                        ploge("isSatelliteAttachEnabledForCarrierByUser: invalid subId, subId="
                                 + subId);
                         return false;
                     }
 
                     if (enabled.isEmpty()) {
-                        loge("isSatelliteAttachEnabledForCarrierByUser: no data for subId(" + subId
+                        ploge("isSatelliteAttachEnabledForCarrierByUser: no data for subId(" + subId
                                 + ")");
                         return false;
                     }
@@ -3510,7 +4107,7 @@
                         return result;
                     }
                 } catch (IllegalArgumentException | SecurityException ex) {
-                    loge("isSatelliteAttachEnabledForCarrierByUser: ex=" + ex);
+                    ploge("isSatelliteAttachEnabledForCarrierByUser: ex=" + ex);
                     return false;
                 }
             }
@@ -3533,19 +4130,19 @@
 
     private void updateRestrictReasonForEntitlementPerCarrier(int subId) {
         if (!getConfigForSubId(subId).getBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false)) {
-            logd("don't support entitlement");
+            plogd("don't support entitlement");
             return;
         }
 
         IIntegerConsumer callback = new IIntegerConsumer.Stub() {
             @Override
             public void accept(int result) {
-                logd("updateRestrictReasonForEntitlementPerCarrier:" + result);
+                plogd("updateRestrictReasonForEntitlementPerCarrier:" + result);
             }
         };
         synchronized (mSupportedSatelliteServicesLock) {
             if (mSatelliteEntitlementStatusPerCarrier.indexOfKey(subId) < 0) {
-                logd("updateRestrictReasonForEntitlementPerCarrier: no correspondent cache, "
+                plogd("updateRestrictReasonForEntitlementPerCarrier: no correspondent cache, "
                         + "load from persist storage");
                 String entitlementStatus = null;
                 try {
@@ -3554,17 +4151,17 @@
                                     SATELLITE_ENTITLEMENT_STATUS, mContext.getOpPackageName(),
                                     mContext.getAttributionTag());
                 } catch (IllegalArgumentException | SecurityException e) {
-                    loge("updateRestrictReasonForEntitlementPerCarrier, e=" + e);
+                    ploge("updateRestrictReasonForEntitlementPerCarrier, e=" + e);
                 }
 
                 if (entitlementStatus == null) {
-                    loge("updateRestrictReasonForEntitlementPerCarrier: invalid subId, subId="
+                    ploge("updateRestrictReasonForEntitlementPerCarrier: invalid subId, subId="
                             + subId + " set to default value");
                     entitlementStatus = "0";
                 }
 
                 if (entitlementStatus.isEmpty()) {
-                    loge("updateRestrictReasonForEntitlementPerCarrier: no data for subId(" + subId
+                    ploge("updateRestrictReasonForEntitlementPerCarrier: no data for subId(" + subId
                             + "). set to default value");
                     entitlementStatus = "0";
                 }
@@ -3589,9 +4186,9 @@
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected boolean persistSatelliteAttachEnabledForCarrierSetting(int subId) {
-        logd("persistSatelliteAttachEnabledForCarrierSetting");
+        plogd("persistSatelliteAttachEnabledForCarrierSetting");
         if (!isValidSubscriptionId(subId)) {
-            loge("persistSatelliteAttachEnabledForCarrierSetting: subId is not valid,"
+            ploge("persistSatelliteAttachEnabledForCarrierSetting: subId is not valid,"
                     + " subId=" + subId);
             return false;
         }
@@ -3604,7 +4201,7 @@
                                 .contains(SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER)
                                 ? "0" : "1");
             } catch (IllegalArgumentException | SecurityException ex) {
-                loge("persistSatelliteAttachEnabledForCarrierSetting, ex=" + ex);
+                ploge("persistSatelliteAttachEnabledForCarrierSetting, ex=" + ex);
                 return false;
             }
         }
@@ -3653,12 +4250,12 @@
     private void evaluateEnablingSatelliteForCarrier(int subId, int reason,
             @Nullable Consumer<Integer> callback) {
         if (callback == null) {
-            callback = errorCode -> logd("evaluateEnablingSatelliteForCarrier: "
+            callback = errorCode -> plogd("evaluateEnablingSatelliteForCarrier: "
                     + "SetSatelliteAttachEnableForCarrier error code =" + errorCode);
         }
 
         if (!isSatelliteSupportedViaCarrier(subId)) {
-            logd("Satellite for carrier is not supported. Only user setting is stored");
+            plogd("Satellite for carrier is not supported. Only user setting is stored");
             callback.accept(SATELLITE_RESULT_SUCCESS);
             return;
         }
@@ -3690,17 +4287,17 @@
     @SatelliteManager.SatelliteResult private int evaluateOemSatelliteRequestAllowed(
             boolean isProvisionRequired) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("oemEnabledSatelliteFlag is disabled");
+            plogd("oemEnabledSatelliteFlag is disabled");
             return SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
         }
         if (!mSatelliteModemInterface.isSatelliteServiceSupported()) {
-            logd("evaluateOemSatelliteRequestAllowed: satellite service is not supported");
+            plogd("evaluateOemSatelliteRequestAllowed: satellite service is not supported");
             return SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED;
         }
 
         Boolean satelliteSupported = isSatelliteSupportedViaOemInternal();
         if (satelliteSupported == null) {
-            logd("evaluateOemSatelliteRequestAllowed: satelliteSupported is null");
+            plogd("evaluateOemSatelliteRequestAllowed: satelliteSupported is null");
             return SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
         }
         if (!satelliteSupported) {
@@ -3710,7 +4307,7 @@
         if (isProvisionRequired) {
             Boolean satelliteProvisioned = isSatelliteViaOemProvisioned();
             if (satelliteProvisioned == null) {
-                logd("evaluateOemSatelliteRequestAllowed: satelliteProvisioned is null");
+                plogd("evaluateOemSatelliteRequestAllowed: satelliteProvisioned is null");
                 return SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
             }
             if (!satelliteProvisioned) {
@@ -3739,10 +4336,12 @@
     private void sendErrorAndReportSessionMetrics(@SatelliteManager.SatelliteResult int error,
             Consumer<Integer> result) {
         result.accept(error);
-        SessionMetricsStats.getInstance()
-                .setInitializationResult(error)
-                .setRadioTechnology(getSupportedNtnRadioTechnology())
+        mSessionMetricsStats.setInitializationResult(error)
+                .setSatelliteTechnology(getSupportedNtnRadioTechnology())
+                .setIsDemoMode(mIsDemoModeEnabled)
                 .reportSessionMetrics();
+        mSessionStartTimeStamp = 0;
+        mSessionProcessingTimeStamp = 0;
     }
 
     private void registerForServiceStateChanged() {
@@ -3761,49 +4360,118 @@
 
     private void handleServiceStateForSatelliteConnectionViaCarrier() {
         for (Phone phone : PhoneFactory.getPhones()) {
+            int subId = phone.getSubId();
             ServiceState serviceState = phone.getServiceState();
-            if (serviceState != null) {
-                synchronized (mSatelliteConnectedLock) {
-                    if (serviceState.isUsingNonTerrestrialNetwork()) {
-                        mWasSatelliteConnectedViaCarrier.put(phone.getSubId(), true);
-                        mIsSatelliteConnectedViaCarrierHysteresisTimeExpired.put(
-                                phone.getSubId(), false);
-                    } else {
-                        Boolean connected = mWasSatelliteConnectedViaCarrier.get(phone.getSubId());
-                        if (connected != null && connected) {
-                            // The device just got disconnected from a satellite network.
-                            mLastSatelliteDisconnectedTimesMillis.put(
-                                    phone.getSubId(), getElapsedRealtime());
-                            mIsSatelliteConnectedViaCarrierHysteresisTimeExpired.put(
-                                    phone.getSubId(), false);
+            if (serviceState == null) {
+                continue;
+            }
+
+            synchronized (mSatelliteConnectedLock) {
+                CarrierRoamingSatelliteSessionStats sessionStats =
+                        mCarrierRoamingSatelliteSessionStatsMap.get(subId);
+
+                if (serviceState.isUsingNonTerrestrialNetwork()) {
+                    if (sessionStats != null) {
+                        sessionStats.onSignalStrength(phone);
+                        if (!mWasSatelliteConnectedViaCarrier.get(subId)) {
+                            // Log satellite connection start
+                            sessionStats.onConnectionStart(phone);
                         }
-                        mWasSatelliteConnectedViaCarrier.put(phone.getSubId(), false);
                     }
+
+                    resetCarrierRoamingSatelliteModeParams(subId);
+                    mWasSatelliteConnectedViaCarrier.put(subId, true);
+
+                    for (NetworkRegistrationInfo nri
+                            : serviceState.getNetworkRegistrationInfoList()) {
+                        if (nri.isNonTerrestrialNetwork()) {
+                            mSatModeCapabilitiesForCarrierRoaming.put(subId,
+                                    nri.getAvailableServices());
+                        }
+                    }
+                } else {
+                    Boolean connected = mWasSatelliteConnectedViaCarrier.get(subId);
+                    if (getWwanIsInService(serviceState)) {
+                        resetCarrierRoamingSatelliteModeParams(subId);
+                    } else if (connected != null && connected) {
+                        // The device just got disconnected from a satellite network
+                        // and is not connected to any terrestrial network that  has coverage
+                        mLastSatelliteDisconnectedTimesMillis.put(subId, getElapsedRealtime());
+
+                        plogd("sendMessageDelayed subId:" + subId
+                                + " phoneId:" + phone.getPhoneId()
+                                + " time:" + getSatelliteConnectionHysteresisTimeMillis(subId));
+                        sendMessageDelayed(obtainMessage(EVENT_NOTIFY_NTN_HYSTERESIS_TIMED_OUT,
+                                        phone.getPhoneId()),
+                                getSatelliteConnectionHysteresisTimeMillis(subId));
+
+                        if (sessionStats != null) {
+                            // Log satellite connection end
+                            sessionStats.onConnectionEnd();
+                        }
+                    }
+                    mWasSatelliteConnectedViaCarrier.put(subId, false);
                 }
+                updateLastNotifiedNtnModeAndNotify(phone);
+            }
+        }
+    }
+
+    private void updateLastNotifiedNtnModeAndNotify(@Nullable Phone phone) {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) return;
+
+        if (phone == null) {
+            return;
+        }
+
+        int subId = phone.getSubId();
+        synchronized (mSatelliteConnectedLock) {
+            boolean initialized = mInitialized.get(subId);
+            boolean lastNotifiedNtnMode = mLastNotifiedNtnMode.get(subId);
+            boolean currNtnMode = isInSatelliteModeForCarrierRoaming(phone);
+            if (!initialized || lastNotifiedNtnMode != currNtnMode) {
+                if (!initialized) mInitialized.put(subId, true);
+                mLastNotifiedNtnMode.put(subId, currNtnMode);
+                phone.notifyCarrierRoamingNtnModeChanged(currNtnMode);
+                logCarrierRoamingSatelliteSessionStats(phone, lastNotifiedNtnMode, currNtnMode);
+            }
+        }
+    }
+
+    private void logCarrierRoamingSatelliteSessionStats(@NonNull Phone phone,
+            boolean lastNotifiedNtnMode, boolean currNtnMode) {
+        synchronized (mSatelliteConnectedLock) {
+            int subId = phone.getSubId();
+            if (!lastNotifiedNtnMode && currNtnMode) {
+                // Log satellite session start
+                CarrierRoamingSatelliteSessionStats sessionStats =
+                        CarrierRoamingSatelliteSessionStats.getInstance(subId);
+                sessionStats.onSessionStart(phone.getCarrierId(), phone);
+                mCarrierRoamingSatelliteSessionStatsMap.put(subId, sessionStats);
+            } else if (lastNotifiedNtnMode && !currNtnMode) {
+                // Log satellite session end
+                CarrierRoamingSatelliteSessionStats sessionStats =
+                        mCarrierRoamingSatelliteSessionStatsMap.get(subId);
+                sessionStats.onSessionEnd();
+                mCarrierRoamingSatelliteSessionStatsMap.remove(subId);
             }
         }
     }
 
     private long getSatelliteConnectionHysteresisTimeMillis(int subId) {
-        synchronized (mCarrierConfigArrayLock) {
-            PersistableBundle config = mCarrierConfigArray.get(subId);
-            if (config == null) {
-                config = getConfigForSubId(subId);
-                mCarrierConfigArray.put(subId, config);
-            }
-            return (config.getInt(
-                    KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT) * 1000L);
-        }
+        PersistableBundle config = getPersistableBundle(subId);
+        return (config.getInt(
+                KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT) * 1000L);
     }
 
     private void persistOemEnabledSatelliteProvisionStatus(boolean isProvisioned) {
         synchronized (mSatelliteViaOemProvisionLock) {
-            logd("persistOemEnabledSatelliteProvisionStatus: isProvisioned=" + isProvisioned);
+            plogd("persistOemEnabledSatelliteProvisionStatus: isProvisioned=" + isProvisioned);
 
             if (!loadSatelliteSharedPreferences()) return;
 
             if (mSharedPreferences == null) {
-                loge("persistOemEnabledSatelliteProvisionStatus: mSharedPreferences is null");
+                ploge("persistOemEnabledSatelliteProvisionStatus: mSharedPreferences is null");
             } else {
                 mSharedPreferences.edit().putBoolean(
                         OEM_ENABLED_SATELLITE_PROVISION_STATUS_KEY, isProvisioned).apply();
@@ -3816,7 +4484,7 @@
             if (!loadSatelliteSharedPreferences()) return false;
 
             if (mSharedPreferences == null) {
-                loge("getPersistedOemEnabledSatelliteProvisionStatus: mSharedPreferences is null");
+                ploge("getPersistedOemEnabledSatelliteProvisionStatus: mSharedPreferences is null");
                 return false;
             } else {
                 return mSharedPreferences.getBoolean(
@@ -3832,7 +4500,7 @@
                         mContext.getSharedPreferences(SATELLITE_SHARED_PREF,
                                 Context.MODE_PRIVATE);
             } catch (Exception e) {
-                loge("loadSatelliteSharedPreferences: Cannot get default "
+                ploge("loadSatelliteSharedPreferences: Cannot get default "
                         + "shared preferences, e=" + e);
                 return false;
             }
@@ -3847,18 +4515,18 @@
         boolean isSatelliteProvisionedInModem = false;
         if (error == SATELLITE_RESULT_SUCCESS) {
             if (ar.result == null) {
-                loge("handleIsSatelliteProvisionedDoneEvent: result is 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) {
-            logd("handleIsSatelliteProvisionedDoneEvent: Modem does not support this request");
+            plogd("handleIsSatelliteProvisionedDoneEvent: Modem does not support this request");
             isSatelliteProvisionedInModem = true;
         }
         boolean isSatelliteViaOemProvisioned =
                 isSatelliteProvisionedInModem && getPersistedOemEnabledSatelliteProvisionStatus();
-        logd("isSatelliteProvisionedInModem=" + isSatelliteProvisionedInModem
+        plogd("isSatelliteProvisionedInModem=" + isSatelliteProvisionedInModem
                 + ", isSatelliteViaOemProvisioned=" + isSatelliteViaOemProvisioned);
         Bundle bundle = new Bundle();
         bundle.putBoolean(SatelliteManager.KEY_SATELLITE_PROVISIONED, isSatelliteViaOemProvisioned);
@@ -3877,11 +4545,11 @@
             @NonNull RequestSatelliteEnabledArgument argument) {
         synchronized (mSatelliteEnabledRequestLock) {
             if (hasMessages(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT, argument)) {
-                logd("WaitForSatelliteEnablingResponseTimer of request ID "
+                plogd("WaitForSatelliteEnablingResponseTimer of request ID "
                         + argument.requestId + " was already started");
                 return;
             }
-            logd("Start timer to wait for response of the satellite enabling request ID="
+            plogd("Start timer to wait for response of the satellite enabling request ID="
                     + argument.requestId + ", enableSatellite=" + argument.enableSatellite
                     + ", mWaitTimeForSatelliteEnablingResponse="
                     + mWaitTimeForSatelliteEnablingResponse);
@@ -3893,7 +4561,7 @@
     private void stopWaitForSatelliteEnablingResponseTimer(
             @NonNull RequestSatelliteEnabledArgument argument) {
         synchronized (mSatelliteEnabledRequestLock) {
-            logd("Stop timer to wait for response of the satellite enabling request ID="
+            plogd("Stop timer to wait for response of the satellite enabling request ID="
                     + argument.requestId + ", enableSatellite=" + argument.enableSatellite);
             removeMessages(EVENT_WAIT_FOR_SATELLITE_ENABLING_RESPONSE_TIMED_OUT, argument);
         }
@@ -3911,7 +4579,7 @@
 
     private void handleEventWaitForSatelliteEnablingResponseTimedOut(
             @NonNull RequestSatelliteEnabledArgument argument) {
-        logw("Timed out to wait for response of the satellite enabling request ID="
+        plogw("Timed out to wait for response of the satellite enabling request ID="
                 + argument.requestId + ", enableSatellite=" + argument.enableSatellite);
 
         synchronized (mSatelliteEnabledRequestLock) {
@@ -3936,24 +4604,29 @@
                     IIntegerConsumer callback = new IIntegerConsumer.Stub() {
                         @Override
                         public void accept(int result) {
-                            logd("handleEventWaitForSatelliteEnablingResponseTimedOut: "
+                            plogd("handleEventWaitForSatelliteEnablingResponseTimedOut: "
                                     + "disable satellite result=" + result);
                         }
                     };
                     Consumer<Integer> result =
                             FunctionalUtils.ignoreRemoteException(callback::accept);
                     RequestSatelliteEnabledArgument request = new RequestSatelliteEnabledArgument(
-                            false, false, result);
+                            false, false, false, result);
                     synchronized (mSatelliteEnabledRequestLock) {
                         mSatelliteEnabledRequest = request;
                     }
                     sendRequestAsync(CMD_SET_SATELLITE_ENABLED, request, null);
                 }
+                notifyEnablementFailedToSatelliteSessionController();
                 mControllerMetricsStats.reportServiceEnablementFailCount();
-                SessionMetricsStats.getInstance()
-                        .setInitializationResult(SATELLITE_RESULT_MODEM_TIMEOUT)
-                        .setRadioTechnology(getSupportedNtnRadioTechnology())
+                mSessionMetricsStats.setInitializationResult(SATELLITE_RESULT_MODEM_TIMEOUT)
+                        .setSatelliteTechnology(getSupportedNtnRadioTechnology())
+                        .setInitializationProcessingTime(
+                                System.currentTimeMillis() - mSessionProcessingTimeStamp)
+                        .setIsDemoMode(mIsDemoModeEnabled)
                         .reportSessionMetrics();
+                mSessionStartTimeStamp = 0;
+                mSessionProcessingTimeStamp = 0;
             } else {
                 /*
                  * Unregister Importance Listener for Pointing UI when Satellite is disabled
@@ -3967,10 +4640,62 @@
                 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;
             }
         }
     }
 
+    private void handleCmdUpdateNtnSignalStrengthReporting(boolean shouldReport) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            plogd("handleCmdUpdateNtnSignalStrengthReporting: oemEnabledSatelliteFlag is "
+                    + "disabled");
+            return;
+        }
+
+        if (!isSatelliteEnabled()) {
+            plogd("handleCmdUpdateNtnSignalStrengthReporting: ignore request, satellite is "
+                    + "disabled");
+            return;
+        }
+
+        mLatestRequestedStateForNtnSignalStrengthReport.set(shouldReport);
+        if (mIsModemEnabledReportingNtnSignalStrength.get() == shouldReport) {
+            plogd("handleCmdUpdateNtnSignalStrengthReporting: ignore request. "
+                    + "mIsModemEnabledReportingNtnSignalStrength="
+                    + mIsModemEnabledReportingNtnSignalStrength.get());
+            return;
+        }
+
+        updateNtnSignalStrengthReporting(shouldReport);
+    }
+
+    private void updateNtnSignalStrengthReporting(boolean shouldReport) {
+        if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
+            plogd("updateNtnSignalStrengthReporting: oemEnabledSatelliteFlag is "
+                    + "disabled");
+            return;
+        }
+
+        SatelliteControllerHandlerRequest request = new SatelliteControllerHandlerRequest(
+                shouldReport, SatelliteServiceUtils.getPhone());
+        Message onCompleted = obtainMessage(EVENT_UPDATE_NTN_SIGNAL_STRENGTH_REPORTING_DONE,
+                request);
+        if (shouldReport) {
+            plogd("updateNtnSignalStrengthReporting: startSendingNtnSignalStrength");
+            mSatelliteModemInterface.startSendingNtnSignalStrength(onCompleted);
+        } else {
+            plogd("updateNtnSignalStrengthReporting: stopSendingNtnSignalStrength");
+            mSatelliteModemInterface.stopSendingNtnSignalStrength(onCompleted);
+        }
+    }
+
     /**
      * This API can be used by only CTS to override the cached value for the device overlay config
      * value : config_send_satellite_datagram_to_modem_in_demo_mode, which determines whether
@@ -3983,12 +4708,12 @@
      */
     public boolean setShouldSendDatagramToModemInDemoMode(boolean shouldSendToModemInDemoMode) {
         if (!mFeatureFlags.oemEnabledSatelliteFlag()) {
-            logd("setShouldSendDatagramToModemInDemoMode: oemEnabledSatelliteFlag is disabled");
+            plogd("setShouldSendDatagramToModemInDemoMode: oemEnabledSatelliteFlag is disabled");
             return false;
         }
 
         if (!isMockModemAllowed()) {
-            logd("setShouldSendDatagramToModemInDemoMode: mock modem not allowed.");
+            plogd("setShouldSendDatagramToModemInDemoMode: mock modem not allowed.");
             return false;
         }
 
@@ -4025,7 +4750,7 @@
     }
 
     private void showSatelliteSystemNotification(int subId) {
-        logd("showSatelliteSystemNotification");
+        plogd("showSatelliteSystemNotification");
         final NotificationChannel notificationChannel = new NotificationChannel(
                 NOTIFICATION_CHANNEL_ID,
                 NOTIFICATION_CHANNEL,
@@ -4041,20 +4766,37 @@
                         R.string.satellite_notification_title))
                 .setContentText(mContext.getResources().getString(
                         R.string.satellite_notification_summary))
-                .setSmallIcon(R.drawable.ic_satellite_alt_24px)
+                .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 `What to expect` dialog of Messaging application.
-        Intent intentOpenMessage = new Intent(Intent.ACTION_VIEW);
-        intentOpenMessage.setData(Uri.parse("sms:"));
-        // TODO : b/322733285 add putExtra to invoke "What to expect" dialog.
-        PendingIntent pendingIntentOpenMessage = PendingIntent.getActivity(mContext, 0,
-                intentOpenMessage, PendingIntent.FLAG_IMMUTABLE);
+        // 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);
         Notification.Action actionOpenMessage = new Notification.Action.Builder(0,
                 mContext.getResources().getString(R.string.satellite_notification_open_message),
                 pendingIntentOpenMessage).build();
@@ -4073,6 +4815,108 @@
 
         notificationManager.notifyAsUser(NOTIFICATION_TAG, NOTIFICATION_ID,
                 notificationBuilder.build(), UserHandle.ALL);
+
+        mCarrierRoamingSatelliteControllerStats.reportCountOfSatelliteNotificationDisplayed();
+    }
+
+    private void resetCarrierRoamingSatelliteModeParams() {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) return;
+
+        for (Phone phone : PhoneFactory.getPhones()) {
+            resetCarrierRoamingSatelliteModeParams(phone.getSubId());
+        }
+    }
+
+    private void resetCarrierRoamingSatelliteModeParams(int subId) {
+        if (!mFeatureFlags.carrierEnabledSatelliteFlag()) return;
+
+        synchronized (mSatelliteConnectedLock) {
+            mLastSatelliteDisconnectedTimesMillis.put(subId, null);
+            mSatModeCapabilitiesForCarrierRoaming.remove(subId);
+            mWasSatelliteConnectedViaCarrier.put(subId, false);
+        }
+    }
+
+    @NonNull
+    private PersistableBundle getPersistableBundle(int subId) {
+        synchronized (mCarrierConfigArrayLock) {
+            PersistableBundle config = mCarrierConfigArray.get(subId);
+            if (config == null) {
+                config = getConfigForSubId(subId);
+                mCarrierConfigArray.put(subId, config);
+            }
+            return config;
+        }
+    }
+
+    // Should be invoked only when session termination done or session termination failed.
+    private int calculateSessionDurationTimeSec() {
+        return (int) (
+                (System.currentTimeMillis() - mSessionStartTimeStamp
+                - mSessionMetricsStats.getSessionInitializationProcessingTimeMillis()
+                - mSessionMetricsStats.getSessionTerminationProcessingTimeMillis()) / 1000);
+    }
+
+    private void notifyEnablementFailedToSatelliteSessionController() {
+        if (mSatelliteSessionController != null) {
+            mSatelliteSessionController.onSatelliteEnablementFailed();
+        } else {
+            ploge("notifyEnablementFailedToSatelliteSessionController: mSatelliteSessionController"
+                    + " is not initialized yet");
+        }
+    }
+
+    private long getDemoPointingAlignedDurationMillisFromResources() {
+        long durationMillis = 15000L;
+        try {
+            durationMillis = mContext.getResources().getInteger(
+                    R.integer.config_demo_pointing_aligned_duration_millis);
+        } catch (Resources.NotFoundException ex) {
+            loge("getPointingAlignedDurationMillis: ex=" + ex);
+        }
+
+        return durationMillis;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public long getDemoPointingAlignedDurationMillis() {
+        return mDemoPointingAlignedDurationMillis;
+    }
+
+    private long getDemoPointingNotAlignedDurationMillisFromResources() {
+        long durationMillis = 30000L;
+        try {
+            durationMillis = mContext.getResources().getInteger(
+                    R.integer.config_demo_pointing_not_aligned_duration_millis);
+        } catch (Resources.NotFoundException ex) {
+            loge("getPointingNotAlignedDurationMillis: ex=" + ex);
+        }
+
+        return durationMillis;
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public long getDemoPointingNotAlignedDurationMillis() {
+        return mDemoPointingNotAlignedDurationMillis;
+    }
+
+    private boolean getWwanIsInService(ServiceState serviceState) {
+        List<NetworkRegistrationInfo> nriList = serviceState
+                .getNetworkRegistrationInfoListForTransportType(
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        for (NetworkRegistrationInfo nri : nriList) {
+            if (nri.isInService()) {
+                logv("getWwanIsInService: return true");
+                return true;
+            }
+        }
+
+        logv("getWwanIsInService: return false");
+        return false;
+    }
+
+    private static void logv(@NonNull String log) {
+        Rlog.v(TAG, log);
     }
 
     private static void logd(@NonNull String log) {
@@ -4086,4 +4930,98 @@
     private static void loge(@NonNull String log) {
         Rlog.e(TAG, log);
     }
+
+    private boolean isSatellitePersistentLoggingEnabled(
+            @NonNull Context context, @NonNull FeatureFlags featureFlags) {
+        if (featureFlags.satellitePersistentLogging()) {
+            return true;
+        }
+        try {
+            return context.getResources().getBoolean(
+                    R.bool.config_dropboxmanager_persistent_logging_enabled);
+        } catch (RuntimeException e) {
+            return false;
+        }
+    }
+
+    private void plogd(@NonNull String log) {
+        Rlog.d(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.debug(TAG, log);
+        }
+    }
+
+    private void plogw(@NonNull String log) {
+        Rlog.w(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.warn(TAG, log);
+        }
+    }
+
+    private void ploge(@NonNull String log) {
+        Rlog.e(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.error(TAG, log);
+        }
+    }
+
+    private void handlePersistentLoggingOnSessionStart(RequestSatelliteEnabledArgument argument) {
+        if (mPersistentLogger == null) {
+            return;
+        }
+        if (argument.isEmergency) {
+            DropBoxManagerLoggerBackend.getInstance(mContext).setLoggingEnabled(true);
+        }
+    }
+
+    private void handlePersistentLoggingOnSessionEnd(boolean isEmergency) {
+        if (mPersistentLogger == null) {
+            return;
+        }
+        DropBoxManagerLoggerBackend loggerBackend =
+                DropBoxManagerLoggerBackend.getInstance(mContext);
+        // Flush persistent satellite logs on eSOS session end
+        if (isEmergency) {
+            loggerBackend.flushAsync();
+        }
+        // Also turn off persisted logging until new session is started
+        loggerBackend.setLoggingEnabled(false);
+    }
+
+    /**
+     * Set last emergency call time to the current time.
+     */
+    public void setLastEmergencyCallTime() {
+        synchronized (mLock) {
+            mLastEmergencyCallTime = getElapsedRealtime();
+            plogd("mLastEmergencyCallTime=" + mLastEmergencyCallTime);
+        }
+    }
+
+    /**
+     * Check if satellite is in emergency mode.
+     */
+    public boolean isInEmergencyMode() {
+        synchronized (mLock) {
+            if (mLastEmergencyCallTime == 0) return false;
+
+            long currentTime = getElapsedRealtime();
+            if ((currentTime - mLastEmergencyCallTime) <= mSatelliteEmergencyModeDurationMillis) {
+                plogd("Satellite is in emergency mode");
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private long getSatelliteEmergencyModeDurationFromOverlayConfig(@NonNull Context context) {
+        Integer duration = DEFAULT_SATELLITE_EMERGENCY_MODE_DURATION_SECONDS;
+        try {
+            duration = context.getResources().getInteger(com.android.internal.R.integer
+                    .config_satellite_emergency_mode_duration);
+        } catch (Resources.NotFoundException ex) {
+            ploge("getSatelliteEmergencyModeDurationFromOverlayConfig: got ex=" + ex);
+        }
+        return TimeUnit.SECONDS.toMillis(duration);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
index 2f86eea..da4c69b 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteModemInterface.java
@@ -30,8 +30,10 @@
 import android.os.Message;
 import android.os.RegistrantList;
 import android.os.RemoteException;
+import android.telephony.DropBoxManagerLoggerBackend;
 import android.telephony.IBooleanConsumer;
 import android.telephony.IIntegerConsumer;
+import android.telephony.PersistentLogger;
 import android.telephony.Rlog;
 import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteCapabilities;
@@ -42,6 +44,7 @@
 import android.telephony.satellite.stub.ISatellite;
 import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
 import android.telephony.satellite.stub.ISatelliteListener;
+import android.telephony.satellite.stub.SatelliteModemState;
 import android.telephony.satellite.stub.SatelliteService;
 import android.text.TextUtils;
 import android.util.Pair;
@@ -49,6 +52,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.ExponentialBackoff;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import java.util.Arrays;
 import java.util.List;
@@ -64,6 +68,9 @@
 
     @NonNull private static SatelliteModemInterface sInstance;
     @NonNull private final Context mContext;
+    @NonNull private final DemoSimulator mDemoSimulator;
+    @NonNull private final SatelliteListener mVendorListener;
+    @NonNull private final SatelliteListener mDemoListener;
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @NonNull protected final ExponentialBackoff mExponentialBackoff;
     @NonNull private final Object mLock = new Object();
@@ -77,6 +84,7 @@
     @NonNull private String mVendorSatellitePackageName = "";
     private boolean mIsBound;
     private boolean mIsBinding;
+    @Nullable private PersistentLogger mPersistentLogger = null;
 
     @NonNull private final RegistrantList mSatelliteProvisionStateChangedRegistrants =
             new RegistrantList();
@@ -93,8 +101,17 @@
             new RegistrantList();
     @NonNull private final RegistrantList mSatelliteCapabilitiesChangedRegistrants =
             new RegistrantList();
+    @NonNull private final RegistrantList mSatelliteSupportedStateChangedRegistrants =
+            new RegistrantList();
 
-    @NonNull private final ISatelliteListener mListener = new ISatelliteListener.Stub() {
+    private class SatelliteListener extends ISatelliteListener.Stub {
+
+        private final boolean mIsDemoListener;
+
+        SatelliteListener(boolean isDemoListener) {
+            mIsDemoListener = isDemoListener;
+        }
+
         @Override
         public void onSatelliteProvisionStateChanged(boolean provisioned) {
             mSatelliteProvisionStateChangedRegistrants.notifyResult(provisioned);
@@ -103,15 +120,19 @@
         @Override
         public void onSatelliteDatagramReceived(
                 android.telephony.satellite.stub.SatelliteDatagram datagram, int pendingCount) {
-            logd("onSatelliteDatagramReceived: pendingCount=" + pendingCount);
-            mSatelliteDatagramsReceivedRegistrants.notifyResult(new Pair<>(
-                    SatelliteServiceUtils.fromSatelliteDatagram(datagram), pendingCount));
+            if (notifyResultIfExpectedListener()) {
+                plogd("onSatelliteDatagramReceived: pendingCount=" + pendingCount);
+                mSatelliteDatagramsReceivedRegistrants.notifyResult(new Pair<>(
+                        SatelliteServiceUtils.fromSatelliteDatagram(datagram), pendingCount));
+            }
         }
 
         @Override
         public void onPendingDatagrams() {
-            logd("onPendingDatagrams");
-            mPendingDatagramsRegistrants.notifyResult(null);
+            if (notifyResultIfExpectedListener()) {
+                plogd("onPendingDatagrams");
+                mPendingDatagramsRegistrants.notifyResult(null);
+            }
         }
 
         @Override
@@ -123,33 +144,39 @@
 
         @Override
         public void onSatelliteModemStateChanged(int state) {
-            mSatelliteModemStateChangedRegistrants.notifyResult(
-                    SatelliteServiceUtils.fromSatelliteModemState(state));
-            int datagramTransferState = SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN;
-            switch (state) {
-                case SatelliteManager.SATELLITE_MODEM_STATE_IDLE:
-                    datagramTransferState = SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
-                    break;
-                case SatelliteManager.SATELLITE_MODEM_STATE_LISTENING:
-                    datagramTransferState =
-                            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING;
-                    break;
-                case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING:
-                    datagramTransferState =
-                            SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING;
-                    break;
-                case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING:
-                    // keep previous state as this could be retrying sending or receiving
-                    break;
+            if (notifyModemStateChanged(state)) {
+                mSatelliteModemStateChangedRegistrants.notifyResult(
+                        SatelliteServiceUtils.fromSatelliteModemState(state));
+                int datagramTransferState =
+                        SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN;
+                switch (state) {
+                    case SatelliteManager.SATELLITE_MODEM_STATE_IDLE:
+                        datagramTransferState =
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE;
+                        break;
+                    case SatelliteManager.SATELLITE_MODEM_STATE_LISTENING:
+                        datagramTransferState =
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING;
+                        break;
+                    case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING:
+                        datagramTransferState =
+                                SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING;
+                        break;
+                    case SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_RETRYING:
+                        // keep previous state as this could be retrying sending or receiving
+                        break;
+                }
+                mDatagramTransferStateChangedRegistrants.notifyResult(datagramTransferState);
             }
-            mDatagramTransferStateChangedRegistrants.notifyResult(datagramTransferState);
         }
 
         @Override
         public void onNtnSignalStrengthChanged(
                 android.telephony.satellite.stub.NtnSignalStrength ntnSignalStrength) {
-            mNtnSignalStrengthChangedRegistrants.notifyResult(
-                    SatelliteServiceUtils.fromNtnSignalStrength(ntnSignalStrength));
+            if (notifyResultIfExpectedListener()) {
+                mNtnSignalStrengthChangedRegistrants.notifyResult(
+                        SatelliteServiceUtils.fromNtnSignalStrength(ntnSignalStrength));
+            }
         }
 
         @Override
@@ -158,7 +185,32 @@
             mSatelliteCapabilitiesChangedRegistrants.notifyResult(
                     SatelliteServiceUtils.fromSatelliteCapabilities(satelliteCapabilities));
         }
-    };
+
+        @Override
+        public void onSatelliteSupportedStateChanged(boolean supported) {
+            mSatelliteSupportedStateChangedRegistrants.notifyResult(supported);
+        }
+
+        @Override
+        public void onRegistrationFailure(int causeCode) {
+            // TO-DO notify registrants
+        }
+
+        private boolean notifyResultIfExpectedListener() {
+            // Demo listener should notify results only during demo mode
+            // Vendor listener should notify result only during real mode
+            return mIsDemoListener == mSatelliteController.isDemoModeEnabled();
+        }
+
+        private boolean notifyModemStateChanged(int state) {
+            if (notifyResultIfExpectedListener()) {
+                return true;
+            }
+
+            return state == SatelliteModemState.SATELLITE_MODEM_STATE_OFF
+                    || state == SatelliteModemState.SATELLITE_MODEM_STATE_UNAVAILABLE;
+        }
+    }
 
     /**
      * @return The singleton instance of SatelliteModemInterface.
@@ -174,13 +226,15 @@
      * Create the SatelliteModemInterface singleton instance.
      * @param context The Context to use to create the SatelliteModemInterface.
      * @param satelliteController The singleton instance of SatelliteController.
+     * @param featureFlags The telephony feature flags.
      * @return The singleton instance of SatelliteModemInterface.
      */
     public static SatelliteModemInterface make(@NonNull Context context,
-            SatelliteController satelliteController) {
+            SatelliteController satelliteController,
+            @NonNull FeatureFlags featureFlags) {
         if (sInstance == null) {
             sInstance = new SatelliteModemInterface(
-                    context, satelliteController, Looper.getMainLooper());
+                    context, satelliteController, Looper.getMainLooper(), featureFlags);
         }
         return sInstance;
     }
@@ -189,12 +243,22 @@
      * Create a SatelliteModemInterface to manage connections to the SatelliteService.
      *
      * @param context The Context for the SatelliteModemInterface.
+     * @param featureFlags The telephony feature flags.
      * @param looper The Looper to run binding retry on.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected SatelliteModemInterface(@NonNull Context context,
-            SatelliteController satelliteController, @NonNull Looper looper) {
+            SatelliteController satelliteController,
+            @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags) {
+        if (isSatellitePersistentLoggingEnabled(context, featureFlags)) {
+            mPersistentLogger = new PersistentLogger(
+                    DropBoxManagerLoggerBackend.getInstance(context));
+        }
         mContext = context;
+        mDemoSimulator = DemoSimulator.make(context, satelliteController);
+        mVendorListener = new SatelliteListener(false);
+        mDemoListener = new SatelliteListener(true);
         mIsSatelliteServiceSupported = getSatelliteServiceSupport();
         mSatelliteController = satelliteController;
         mExponentialBackoff = new ExponentialBackoff(REBIND_INITIAL_DELAY, REBIND_MAXIMUM_DELAY,
@@ -214,7 +278,7 @@
             bindService();
         });
         mExponentialBackoff.start();
-        logd("Created SatelliteModemInterface. Attempting to bind to SatelliteService.");
+        plogd("Created SatelliteModemInterface. Attempting to bind to SatelliteService.");
         bindService();
     }
 
@@ -247,7 +311,7 @@
         }
         String packageName = getSatellitePackageName();
         if (TextUtils.isEmpty(packageName)) {
-            loge("Unable to bind to the satellite service because the package is undefined.");
+            ploge("Unable to bind to the satellite service because the package is undefined.");
             // Since the package name comes from static device configs, stop retry because
             // rebind will continue to fail without a valid package name.
             synchronized (mLock) {
@@ -260,18 +324,18 @@
         intent.setPackage(packageName);
 
         mSatelliteServiceConnection = new SatelliteServiceConnection();
-        logd("Binding to " + packageName);
+        plogd("Binding to " + packageName);
         try {
             boolean success = mContext.bindService(
                     intent, mSatelliteServiceConnection, Context.BIND_AUTO_CREATE);
             if (success) {
-                logd("Successfully bound to the satellite service.");
+                plogd("Successfully bound to the satellite service.");
             } else {
                 synchronized (mLock) {
                     mIsBinding = false;
                 }
                 mExponentialBackoff.notifyFailed();
-                loge("Error binding to the satellite service. Retrying in "
+                ploge("Error binding to the satellite service. Retrying in "
                         + mExponentialBackoff.getCurrentDelay() + " ms.");
             }
         } catch (Exception e) {
@@ -279,7 +343,7 @@
                 mIsBinding = false;
             }
             mExponentialBackoff.notifyFailed();
-            loge("Exception binding to the satellite service. Retrying in "
+            ploge("Exception binding to the satellite service. Retrying in "
                     + mExponentialBackoff.getCurrentDelay() + " ms. Exception: " + e);
         }
     }
@@ -299,7 +363,7 @@
     private class SatelliteServiceConnection implements ServiceConnection {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
-            logd("onServiceConnected: ComponentName=" + name);
+            plogd("onServiceConnected: ComponentName=" + name);
             synchronized (mLock) {
                 mIsBound = true;
                 mIsBinding = false;
@@ -307,17 +371,18 @@
             mSatelliteService = ISatellite.Stub.asInterface(service);
             mExponentialBackoff.stop();
             try {
-                mSatelliteService.setSatelliteListener(mListener);
+                mSatelliteService.setSatelliteListener(mVendorListener);
+                mDemoSimulator.setSatelliteListener(mDemoListener);
             } catch (RemoteException e) {
                 // TODO: Retry setSatelliteListener
-                logd("setSatelliteListener: RemoteException " + e);
+                plogd("setSatelliteListener: RemoteException " + e);
             }
             mSatelliteController.onSatelliteServiceConnected();
         }
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
-            loge("onServiceDisconnected: Waiting for reconnect.");
+            ploge("onServiceDisconnected: Waiting for reconnect.");
             synchronized (mLock) {
                 mIsBinding = false;
             }
@@ -327,7 +392,7 @@
 
         @Override
         public void onBindingDied(ComponentName name) {
-            loge("onBindingDied: Unbinding and rebinding service.");
+            ploge("onBindingDied: Unbinding and rebinding service.");
             synchronized (mLock) {
                 mIsBound = false;
                 mIsBinding = false;
@@ -505,6 +570,27 @@
     }
 
     /**
+     * Registers for the satellite supported state changed.
+     *
+     * @param h Handler for notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSatelliteSupportedStateChanged(
+            @NonNull Handler h, int what, @Nullable Object obj) {
+        mSatelliteSupportedStateChangedRegistrants.add(h, what, obj);
+    }
+
+    /**
+     * Unregisters for the satellite supported state changed.
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSatelliteSupportedStateChanged(@NonNull Handler h) {
+        mSatelliteSupportedStateChangedRegistrants.remove(h);
+    }
+
+    /**
      * Request to enable or disable the satellite service listening mode.
      * Listening mode allows the satellite service to listen for incoming pages.
      *
@@ -522,7 +608,7 @@
                             @Override
                             public void accept(int result) {
                                 int error = SatelliteServiceUtils.fromSatelliteError(result);
-                                logd("requestSatelliteListeningEnabled: " + error);
+                                plogd("requestSatelliteListeningEnabled: " + error);
                                 Binder.withCleanCallingIdentity(() -> {
                                     if (message != null) {
                                         sendMessageWithResult(message, null, error);
@@ -531,14 +617,14 @@
                             }
                         });
             } catch (RemoteException e) {
-                loge("requestSatelliteListeningEnabled: RemoteException " + e);
+                ploge("requestSatelliteListeningEnabled: RemoteException " + e);
                 if (message != null) {
                     sendMessageWithResult(
                             message, null, SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
                 }
             }
         } else {
-            loge("requestSatelliteListeningEnabled: Satellite service is unavailable.");
+            ploge("requestSatelliteListeningEnabled: Satellite service is unavailable.");
             if (message != null) {
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
@@ -556,28 +642,35 @@
             @Nullable Message message) {
         if (mSatelliteService != null) {
             try {
-                mSatelliteService.enableCellularModemWhileSatelliteModeIsOn(enabled,
-                        new IIntegerConsumer.Stub() {
-                            @Override
-                            public void accept(int result) {
-                                int error = SatelliteServiceUtils.fromSatelliteError(result);
-                                logd("enableCellularModemWhileSatelliteModeIsOn: " + error);
-                                Binder.withCleanCallingIdentity(() -> {
-                                        if (message != null) {
-                                            sendMessageWithResult(message, null, error);
-                                        }
-                                });
+                IIntegerConsumer errorCallback = new IIntegerConsumer.Stub() {
+                    @Override
+                    public void accept(int result) {
+                        int error = SatelliteServiceUtils.fromSatelliteError(result);
+                        plogd("enableCellularModemWhileSatelliteModeIsOn: " + error);
+                        Binder.withCleanCallingIdentity(() -> {
+                            if (message != null) {
+                                sendMessageWithResult(message, null, error);
                             }
                         });
+                    }
+                };
+
+                if (mSatelliteController.isDemoModeEnabled()) {
+                    mDemoSimulator.enableCellularModemWhileSatelliteModeIsOn(
+                            enabled, errorCallback);
+                } else {
+                    mSatelliteService.enableCellularModemWhileSatelliteModeIsOn(
+                            enabled, errorCallback);
+                }
             } catch (RemoteException e) {
-                loge("enableCellularModemWhileSatelliteModeIsOn: RemoteException " + e);
+                ploge("enableCellularModemWhileSatelliteModeIsOn: RemoteException " + e);
                 if (message != null) {
                     sendMessageWithResult(
                             message, null, SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
                 }
             }
         } else {
-            loge("enableCellularModemWhileSatelliteModeIsOn: Satellite service is unavailable.");
+            ploge("enableCellularModemWhileSatelliteModeIsOn: Satellite service is unavailable.");
             if (message != null) {
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
@@ -591,29 +684,30 @@
      *
      * @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 message The Message to send to result of the operation to.
      */
     public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
-            @NonNull Message message) {
+            boolean isEmergency, @NonNull Message message) {
         if (mSatelliteService != null) {
             try {
                 mSatelliteService.requestSatelliteEnabled(enableSatellite, enableDemoMode,
-                        new IIntegerConsumer.Stub() {
-                    @Override
-                    public void accept(int result) {
-                        int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        logd("setSatelliteEnabled: " + error);
-                        Binder.withCleanCallingIdentity(() ->
+                        isEmergency, new IIntegerConsumer.Stub() {
+                            @Override
+                            public void accept(int result) {
+                                int error = SatelliteServiceUtils.fromSatelliteError(result);
+                                plogd("setSatelliteEnabled: " + error);
+                                Binder.withCleanCallingIdentity(() ->
                                 sendMessageWithResult(message, null, error));
                     }
                 });
             } catch (RemoteException e) {
-                loge("setSatelliteEnabled: RemoteException " + e);
+                ploge("setSatelliteEnabled: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("setSatelliteEnabled: Satellite service is unavailable.");
+            ploge("setSatelliteEnabled: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -631,7 +725,7 @@
                     @Override
                     public void accept(int result) {
                         int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        logd("requestIsSatelliteEnabled: " + error);
+                        plogd("requestIsSatelliteEnabled: " + error);
                         Binder.withCleanCallingIdentity(() ->
                                 sendMessageWithResult(message, null, error));
                     }
@@ -641,18 +735,18 @@
                         // Convert for compatibility with SatelliteResponse
                         // TODO: This should just report result instead.
                         int[] enabled = new int[] {result ? 1 : 0};
-                        logd("requestIsSatelliteEnabled: " + Arrays.toString(enabled));
+                        plogd("requestIsSatelliteEnabled: " + Arrays.toString(enabled));
                         Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
                                 message, enabled, SatelliteManager.SATELLITE_RESULT_SUCCESS));
                     }
                 });
             } catch (RemoteException e) {
-                loge("requestIsSatelliteEnabled: RemoteException " + e);
+                ploge("requestIsSatelliteEnabled: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("requestIsSatelliteEnabled: Satellite service is unavailable.");
+            ploge("requestIsSatelliteEnabled: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -670,25 +764,25 @@
                     @Override
                     public void accept(int result) {
                         int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        logd("requestIsSatelliteSupported: " + error);
+                        plogd("requestIsSatelliteSupported: " + error);
                         Binder.withCleanCallingIdentity(() ->
                                 sendMessageWithResult(message, null, error));
                     }
                 }, new IBooleanConsumer.Stub() {
                     @Override
                     public void accept(boolean result) {
-                        logd("requestIsSatelliteSupported: " + result);
+                        plogd("requestIsSatelliteSupported: " + result);
                         Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
                                 message, result, SatelliteManager.SATELLITE_RESULT_SUCCESS));
                     }
                 });
             } catch (RemoteException e) {
-                loge("requestIsSatelliteSupported: RemoteException " + e);
+                ploge("requestIsSatelliteSupported: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("requestIsSatelliteSupported: Satellite service is unavailable.");
+            ploge("requestIsSatelliteSupported: Satellite service is unavailable.");
             sendMessageWithResult(
                     message, null, SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -706,7 +800,7 @@
                     @Override
                     public void accept(int result) {
                         int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        logd("requestSatelliteCapabilities: " + error);
+                        plogd("requestSatelliteCapabilities: " + error);
                         Binder.withCleanCallingIdentity(() ->
                                 sendMessageWithResult(message, null, error));
                     }
@@ -716,18 +810,18 @@
                             result) {
                         SatelliteCapabilities capabilities =
                                 SatelliteServiceUtils.fromSatelliteCapabilities(result);
-                        logd("requestSatelliteCapabilities: " + capabilities);
+                        plogd("requestSatelliteCapabilities: " + capabilities);
                         Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
                                 message, capabilities, SatelliteManager.SATELLITE_RESULT_SUCCESS));
                     }
                 });
             } catch (RemoteException e) {
-                loge("requestSatelliteCapabilities: RemoteException " + e);
+                ploge("requestSatelliteCapabilities: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("requestSatelliteCapabilities: Satellite service is unavailable.");
+            ploge("requestSatelliteCapabilities: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -747,18 +841,18 @@
                     @Override
                     public void accept(int result) {
                         int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        logd("startSendingSatellitePointingInfo: " + error);
+                        plogd("startSendingSatellitePointingInfo: " + error);
                         Binder.withCleanCallingIdentity(() ->
                                 sendMessageWithResult(message, null, error));
                     }
                 });
             } catch (RemoteException e) {
-                loge("startSendingSatellitePointingInfo: RemoteException " + e);
+                ploge("startSendingSatellitePointingInfo: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("startSendingSatellitePointingInfo: Satellite service is unavailable.");
+            ploge("startSendingSatellitePointingInfo: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -777,18 +871,18 @@
                     @Override
                     public void accept(int result) {
                         int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        logd("stopSendingSatellitePointingInfo: " + error);
+                        plogd("stopSendingSatellitePointingInfo: " + error);
                         Binder.withCleanCallingIdentity(() ->
                                 sendMessageWithResult(message, null, error));
                     }
                 });
             } catch (RemoteException e) {
-                loge("stopSendingSatellitePointingInfo: RemoteException " + e);
+                ploge("stopSendingSatellitePointingInfo: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("stopSendingSatellitePointingInfo: Satellite service is unavailable.");
+            ploge("stopSendingSatellitePointingInfo: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -813,18 +907,18 @@
                             @Override
                             public void accept(int result) {
                                 int error = SatelliteServiceUtils.fromSatelliteError(result);
-                                logd("provisionSatelliteService: " + error);
+                                plogd("provisionSatelliteService: " + error);
                                 Binder.withCleanCallingIdentity(() ->
                                         sendMessageWithResult(message, null, error));
                             }
                         });
             } catch (RemoteException e) {
-                loge("provisionSatelliteService: RemoteException " + e);
+                ploge("provisionSatelliteService: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("provisionSatelliteService: Satellite service is unavailable.");
+            ploge("provisionSatelliteService: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -845,18 +939,18 @@
                     @Override
                     public void accept(int result) {
                         int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        logd("deprovisionSatelliteService: " + error);
+                        plogd("deprovisionSatelliteService: " + error);
                         Binder.withCleanCallingIdentity(() ->
                                 sendMessageWithResult(message, null, error));
                     }
                 });
             } catch (RemoteException e) {
-                loge("deprovisionSatelliteService: RemoteException " + e);
+                ploge("deprovisionSatelliteService: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("deprovisionSatelliteService: Satellite service is unavailable.");
+            ploge("deprovisionSatelliteService: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -874,7 +968,7 @@
                     @Override
                     public void accept(int result) {
                         int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        logd("requestIsSatelliteProvisioned: " + error);
+                        plogd("requestIsSatelliteProvisioned: " + error);
                         Binder.withCleanCallingIdentity(() ->
                                 sendMessageWithResult(message, null, error));
                     }
@@ -884,18 +978,18 @@
                         // Convert for compatibility with SatelliteResponse
                         // TODO: This should just report result instead.
                         int[] provisioned = new int[] {result ? 1 : 0};
-                        logd("requestIsSatelliteProvisioned: " + Arrays.toString(provisioned));
+                        plogd("requestIsSatelliteProvisioned: " + Arrays.toString(provisioned));
                         Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
                                 message, provisioned, SatelliteManager.SATELLITE_RESULT_SUCCESS));
                     }
                 });
             } catch (RemoteException e) {
-                loge("requestIsSatelliteProvisioned: RemoteException " + e);
+                ploge("requestIsSatelliteProvisioned: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("requestIsSatelliteProvisioned: Satellite service is unavailable.");
+            ploge("requestIsSatelliteProvisioned: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -915,18 +1009,18 @@
                     @Override
                     public void accept(int result) {
                         int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        logd("pollPendingDatagrams: " + error);
+                        plogd("pollPendingDatagrams: " + error);
                         Binder.withCleanCallingIdentity(() ->
                                 sendMessageWithResult(message, null, error));
                     }
                 });
             } catch (RemoteException e) {
-                loge("pollPendingDatagrams: RemoteException " + e);
+                ploge("pollPendingDatagrams: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("pollPendingDatagrams: Satellite service is unavailable.");
+            ploge("pollPendingDatagrams: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -951,18 +1045,18 @@
                             @Override
                             public void accept(int result) {
                                 int error = SatelliteServiceUtils.fromSatelliteError(result);
-                                logd("sendDatagram: " + error);
+                                plogd("sendDatagram: " + error);
                                 Binder.withCleanCallingIdentity(() ->
                                         sendMessageWithResult(message, null, error));
                             }
                         });
             } catch (RemoteException e) {
-                loge("sendDatagram: RemoteException " + e);
+                ploge("sendDatagram: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("sendDatagram: Satellite service is unavailable.");
+            ploge("sendDatagram: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -982,7 +1076,7 @@
                     @Override
                     public void accept(int result) {
                         int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        logd("requestSatelliteModemState: " + error);
+                        plogd("requestSatelliteModemState: " + error);
                         Binder.withCleanCallingIdentity(() ->
                                 sendMessageWithResult(message, null, error));
                     }
@@ -991,60 +1085,18 @@
                     public void accept(int result) {
                         // Convert SatelliteModemState from service to frameworks definition.
                         int modemState = SatelliteServiceUtils.fromSatelliteModemState(result);
-                        logd("requestSatelliteModemState: " + modemState);
+                        plogd("requestSatelliteModemState: " + modemState);
                         Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
                                 message, modemState, SatelliteManager.SATELLITE_RESULT_SUCCESS));
                     }
                 });
             } catch (RemoteException e) {
-                loge("requestSatelliteModemState: RemoteException " + e);
+                ploge("requestSatelliteModemState: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("requestSatelliteModemState: Satellite service is unavailable.");
-            sendMessageWithResult(message, null,
-                    SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
-        }
-    }
-
-    /**
-     * Request to get whether satellite communication is allowed for the current location.
-     *
-     * @param message The Message to send to result of the operation to.
-     */
-    public void requestIsSatelliteCommunicationAllowedForCurrentLocation(@NonNull Message message) {
-        if (mSatelliteService != null) {
-            try {
-                mSatelliteService.requestIsSatelliteCommunicationAllowedForCurrentLocation(
-                        new IIntegerConsumer.Stub() {
-                            @Override
-                            public void accept(int result) {
-                                int error = SatelliteServiceUtils.fromSatelliteError(result);
-                                logd("requestIsCommunicationAllowedForCurrentLocation: "
-                                        + error);
-                                Binder.withCleanCallingIdentity(() ->
-                                        sendMessageWithResult(message, null, error));
-                            }
-                        }, new IBooleanConsumer.Stub() {
-                            @Override
-                            public void accept(boolean result) {
-                                logd("requestIsCommunicationAllowedForCurrentLocation: "
-                                        + result);
-                                Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
-                                        message, result,
-                                        SatelliteManager.SATELLITE_RESULT_SUCCESS));
-                            }
-                        });
-            } catch (RemoteException e) {
-                loge("requestIsCommunicationAllowedForCurrentLocation: RemoteException "
-                        + e);
-                sendMessageWithResult(message, null,
-                        SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
-            }
-        } else {
-            loge("requestIsCommunicationAllowedForCurrentLocation: "
-                    + "Satellite service is unavailable.");
+            ploge("requestSatelliteModemState: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -1065,7 +1117,7 @@
                             @Override
                             public void accept(int result) {
                                 int error = SatelliteServiceUtils.fromSatelliteError(result);
-                                logd("requestTimeForNextSatelliteVisibility: " + error);
+                                plogd("requestTimeForNextSatelliteVisibility: " + error);
                                 Binder.withCleanCallingIdentity(() ->
                                         sendMessageWithResult(message, null, error));
                             }
@@ -1075,19 +1127,19 @@
                                 // Convert for compatibility with SatelliteResponse
                                 // TODO: This should just report result instead.
                                 int[] time = new int[] {result};
-                                logd("requestTimeForNextSatelliteVisibility: "
+                                plogd("requestTimeForNextSatelliteVisibility: "
                                         + Arrays.toString(time));
                                 Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
                                         message, time, SatelliteManager.SATELLITE_RESULT_SUCCESS));
                             }
                         });
             } catch (RemoteException e) {
-                loge("requestTimeForNextSatelliteVisibility: RemoteException " + e);
+                ploge("requestTimeForNextSatelliteVisibility: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("requestTimeForNextSatelliteVisibility: Satellite service is unavailable.");
+            ploge("requestTimeForNextSatelliteVisibility: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -1120,18 +1172,18 @@
                             @Override
                             public void accept(int result) {
                                 int error = SatelliteServiceUtils.fromSatelliteError(result);
-                                logd("setSatellitePlmn: " + error);
+                                plogd("setSatellitePlmn: " + error);
                                 Binder.withCleanCallingIdentity(() ->
                                         sendMessageWithResult(message, null, error));
                             }
                         });
             } catch (RemoteException e) {
-                loge("setSatellitePlmn: RemoteException " + e);
+                ploge("setSatellitePlmn: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("setSatellitePlmn: Satellite service is unavailable.");
+            ploge("setSatellitePlmn: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -1155,18 +1207,18 @@
                             @Override
                             public void accept(int result) {
                                 int error = SatelliteServiceUtils.fromSatelliteError(result);
-                                logd("requestSetSatelliteEnabledForCarrier: " + error);
+                                plogd("requestSetSatelliteEnabledForCarrier: " + error);
                                 Binder.withCleanCallingIdentity(() ->
                                         sendMessageWithResult(message, null, error));
                             }
                         });
             } catch (RemoteException e) {
-                loge("requestSetSatelliteEnabledForCarrier: RemoteException " + e);
+                ploge("requestSetSatelliteEnabledForCarrier: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("requestSetSatelliteEnabledForCarrier: Satellite service is unavailable.");
+            ploge("requestSetSatelliteEnabledForCarrier: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_REQUEST_NOT_SUPPORTED);
         }
@@ -1188,7 +1240,7 @@
                             @Override
                             public void accept(int result) {
                                 int error = SatelliteServiceUtils.fromSatelliteError(result);
-                                logd("requestIsSatelliteEnabledForCarrier: " + error);
+                                plogd("requestIsSatelliteEnabledForCarrier: " + error);
                                 Binder.withCleanCallingIdentity(() ->
                                         sendMessageWithResult(message, null, error));
                             }
@@ -1198,7 +1250,7 @@
                                 // Convert for compatibility with SatelliteResponse
                                 // TODO: This should just report result instead.
                                 int[] enabled = new int[] {result ? 1 : 0};
-                                logd("requestIsSatelliteEnabledForCarrier: "
+                                plogd("requestIsSatelliteEnabledForCarrier: "
                                         + Arrays.toString(enabled));
                                 Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
                                         message, enabled,
@@ -1206,12 +1258,12 @@
                             }
                         });
             } catch (RemoteException e) {
-                loge("requestIsSatelliteEnabledForCarrier: RemoteException " + e);
+                ploge("requestIsSatelliteEnabledForCarrier: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("requestIsSatelliteEnabledForCarrier: Satellite service is unavailable.");
+            ploge("requestIsSatelliteEnabledForCarrier: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -1230,7 +1282,7 @@
                             @Override
                             public void accept(int result) {
                                 int error = SatelliteServiceUtils.fromSatelliteError(result);
-                                logd("requestNtnSignalStrength: " + error);
+                                plogd("requestNtnSignalStrength: " + error);
                                 Binder.withCleanCallingIdentity(() ->
                                         sendMessageWithResult(message, null, error));
                             }
@@ -1240,19 +1292,19 @@
                                     android.telephony.satellite.stub.NtnSignalStrength result) {
                                 NtnSignalStrength ntnSignalStrength =
                                         SatelliteServiceUtils.fromNtnSignalStrength(result);
-                                logd("requestNtnSignalStrength: " + ntnSignalStrength);
+                                plogd("requestNtnSignalStrength: " + ntnSignalStrength);
                                 Binder.withCleanCallingIdentity(() -> sendMessageWithResult(
                                         message, ntnSignalStrength,
                                         SatelliteManager.SATELLITE_RESULT_SUCCESS));
                             }
                         });
             } catch (RemoteException e) {
-                loge("requestNtnSignalStrength: RemoteException " + e);
+                ploge("requestNtnSignalStrength: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("requestNtnSignalStrength: Satellite service is unavailable.");
+            ploge("requestNtnSignalStrength: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -1271,18 +1323,18 @@
                     @Override
                     public void accept(int result) {
                         int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        logd("startSendingNtnSignalStrength: " + error);
+                        plogd("startSendingNtnSignalStrength: " + error);
                         Binder.withCleanCallingIdentity(() ->
                                 sendMessageWithResult(message, null, error));
                     }
                 });
             } catch (RemoteException e) {
-                loge("startSendingNtnSignalStrength: RemoteException " + e);
+                ploge("startSendingNtnSignalStrength: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("startSendingNtnSignalStrength: Satellite service is unavailable.");
+            ploge("startSendingNtnSignalStrength: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -1300,18 +1352,18 @@
                     @Override
                     public void accept(int result) {
                         int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        logd("stopSendingNtnSignalStrength: " + error);
+                        plogd("stopSendingNtnSignalStrength: " + error);
                         Binder.withCleanCallingIdentity(() ->
                                 sendMessageWithResult(message, null, error));
                     }
                 });
             } catch (RemoteException e) {
-                loge("stopSendingNtnSignalStrength: RemoteException " + e);
+                ploge("stopSendingNtnSignalStrength: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("stopSendingNtnSignalStrength: Satellite service is unavailable.");
+            ploge("stopSendingNtnSignalStrength: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -1329,18 +1381,18 @@
                     @Override
                     public void accept(int result) {
                         int error = SatelliteServiceUtils.fromSatelliteError(result);
-                        logd("abortSendingSatelliteDatagrams: " + error);
+                        plogd("abortSendingSatelliteDatagrams: " + error);
                         Binder.withCleanCallingIdentity(() ->
                                 sendMessageWithResult(message, null, error));
                     }
                 });
             } catch (RemoteException e) {
-                loge("abortSendingSatelliteDatagrams: RemoteException " + e);
+                ploge("abortSendingSatelliteDatagrams: RemoteException " + e);
                 sendMessageWithResult(message, null,
                         SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
             }
         } else {
-            loge("abortSendingSatelliteDatagrams: Satellite service is unavailable.");
+            ploge("abortSendingSatelliteDatagrams: Satellite service is unavailable.");
             sendMessageWithResult(message, null,
                     SatelliteManager.SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
         }
@@ -1366,7 +1418,7 @@
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public void setSatelliteServicePackageName(@Nullable String servicePackageName) {
-        logd("setSatelliteServicePackageName: config_satellite_service_package is "
+        plogd("setSatelliteServicePackageName: config_satellite_service_package is "
                 + "updated, new packageName=" + servicePackageName);
         mExponentialBackoff.stop();
         if (mSatelliteServiceConnection != null) {
@@ -1403,4 +1455,31 @@
     private static void loge(@NonNull String log) {
         Rlog.e(TAG, log);
     }
+
+    private boolean isSatellitePersistentLoggingEnabled(
+            @NonNull Context context, @NonNull FeatureFlags featureFlags) {
+        if (featureFlags.satellitePersistentLogging()) {
+            return true;
+        }
+        try {
+            return context.getResources().getBoolean(
+                    R.bool.config_dropboxmanager_persistent_logging_enabled);
+        } catch (RuntimeException e) {
+            return false;
+        }
+    }
+
+    private void plogd(@NonNull String log) {
+        Rlog.d(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.debug(TAG, log);
+        }
+    }
+
+    private void ploge(@NonNull String log) {
+        Rlog.e(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.error(TAG, log);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
index c491476..3fe2970 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommender.java
@@ -24,6 +24,7 @@
 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 com.android.internal.telephony.flags.Flags.satellitePersistentLogging;
 import static com.android.internal.telephony.satellite.SatelliteController.INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE;
 
 import android.annotation.NonNull;
@@ -33,6 +34,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -41,6 +43,10 @@
 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;
@@ -64,6 +70,7 @@
 import com.android.internal.telephony.SmsApplication;
 import com.android.internal.telephony.metrics.SatelliteStats;
 
+import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 
@@ -99,7 +106,8 @@
     private boolean mIsSatelliteAllowedForCurrentLocation = false;
     @GuardedBy("mLock")
     private boolean mCheckingAccessRestrictionInProgress = false;
-    private final long mTimeoutMillis;
+    protected long mTimeoutMillis = 0;
+    private final long mOemEnabledTimeoutMillis;
     private final AtomicBoolean mIsSatelliteConnectedViaCarrierWithinHysteresisTime =
             new AtomicBoolean(false);
     @GuardedBy("mLock")
@@ -107,15 +115,17 @@
     protected int mCountOfTimerStarted = 0;
     private final Object mLock = new Object();
 
+    @Nullable private PersistentLogger mPersistentLogger = null;
+
     /**
      * Create an instance of SatelliteSOSMessageRecommender.
      *
      * @param context The Context for the SatelliteSOSMessageRecommender.
      * @param looper The looper used with the handler of this class.
      */
-    public SatelliteSOSMessageRecommender(@NonNull Context context, @NonNull Looper looper) {
-        this(context, looper, SatelliteController.getInstance(), null,
-                getEmergencyCallWaitForConnectionTimeoutMillis(context));
+    public SatelliteSOSMessageRecommender(@NonNull Context context,
+            @NonNull Looper looper) {
+        this(context, looper, SatelliteController.getInstance(), null);
     }
 
     /**
@@ -127,21 +137,25 @@
      * @param satelliteController The SatelliteController singleton instance.
      * @param imsManager The ImsManager instance associated with the phone, which is used for making
      *                   the emergency call. This argument is not null only in unit tests.
-     * @param timeoutMillis The timeout duration of the timer.
      */
     @VisibleForTesting
     protected SatelliteSOSMessageRecommender(@NonNull Context context, @NonNull Looper looper,
-            @NonNull SatelliteController satelliteController, ImsManager imsManager,
-            long timeoutMillis) {
+            @NonNull SatelliteController satelliteController,
+            ImsManager imsManager) {
         super(looper);
+        if (isSatellitePersistentLoggingEnabled(context)) {
+            mPersistentLogger = new PersistentLogger(
+                    DropBoxManagerLoggerBackend.getInstance(context));
+        }
         mContext = context;
         mSatelliteController = satelliteController;
         mImsManager = imsManager;
-        mTimeoutMillis = timeoutMillis;
+        mOemEnabledTimeoutMillis =
+                getOemEnabledEmergencyCallWaitForConnectionTimeoutMillis(context);
         mISatelliteProvisionStateCallback = new ISatelliteProvisionStateCallback.Stub() {
             @Override
             public void onSatelliteProvisionStateChanged(boolean provisioned) {
-                logd("onSatelliteProvisionStateChanged: provisioned=" + provisioned);
+                plogd("onSatelliteProvisionStateChanged: provisioned=" + provisioned);
                 sendMessage(obtainMessage(EVENT_SATELLITE_PROVISIONED_STATE_CHANGED, provisioned));
             }
         };
@@ -172,7 +186,7 @@
                 handleSatelliteAccessRestrictionCheckingResult((boolean) msg.obj);
                 break;
             default:
-                logd("handleMessage: unexpected message code: " + msg.what);
+                plogd("handleMessage: unexpected message code: " + msg.what);
                 break;
         }
     }
@@ -184,9 +198,13 @@
      *                   call.
      */
     public void onEmergencyCallStarted(@NonNull Connection connection) {
-        if (!mSatelliteController.isSatelliteSupportedViaOem()
-                && !mSatelliteController.isSatelliteSupportedViaCarrier()) {
-            logd("onEmergencyCallStarted: satellite is not supported");
+        if (!isSatelliteSupported()) {
+            plogd("onEmergencyCallStarted: satellite is not supported");
+            return;
+        }
+
+        if (hasMessages(EVENT_EMERGENCY_CALL_STARTED)) {
+            logd("onEmergencyCallStarted: Ignoring due to ongoing event:");
             return;
         }
 
@@ -210,10 +228,9 @@
      */
     public void onEmergencyCallConnectionStateChanged(
             String callId, @Connection.ConnectionState int state) {
-        logd("callId=" + callId + ", state=" + state);
-        if (!mSatelliteController.isSatelliteSupportedViaOem()
-                && !mSatelliteController.isSatelliteSupportedViaCarrier()) {
-            logd("onEmergencyCallConnectionStateChanged: satellite is not supported");
+        plogd("callId=" + callId + ", state=" + state);
+        if (!isSatelliteSupported()) {
+            plogd("onEmergencyCallConnectionStateChanged: satellite is not supported");
             return;
         }
         Pair<String, Integer> argument = new Pair<>(callId, state);
@@ -222,13 +239,17 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected ComponentName getDefaultSmsApp() {
-        return SmsApplication.getDefaultSmsApplication(mContext, false);
+        return SmsApplication.getDefaultSendToApplication(mContext, false);
     }
 
     private void handleEmergencyCallStartedEvent(@NonNull Connection connection) {
+        mSatelliteController.setLastEmergencyCallTime();
+
         if (sendEventDisplayEmergencyMessageForcefully(connection)) {
             return;
         }
+
+        selectEmergencyCallWaitForConnectionTimeoutDuration();
         if (mEmergencyConnection == null) {
             handleStateChangedEventForHysteresisTimer();
             registerForInterestedStateChangedEvents();
@@ -256,12 +277,12 @@
     private void evaluateSendingConnectionEventDisplayEmergencyMessage() {
         synchronized (mLock) {
             if (mEmergencyConnection == null) {
-                loge("No emergency call is ongoing...");
+                ploge("No emergency call is ongoing...");
                 return;
             }
 
             if (!mIsTimerTimedOut || mCheckingAccessRestrictionInProgress) {
-                logd("mIsTimerTimedOut=" + mIsTimerTimedOut
+                plogd("mIsTimerTimedOut=" + mIsTimerTimedOut
                         + ", mCheckingAccessRestrictionInProgress="
                         + mCheckingAccessRestrictionInProgress);
                 return;
@@ -275,18 +296,18 @@
             updateSatelliteViaCarrierAvailability();
 
             boolean isDialerNotified = false;
-            if (!isImsRegistered() && !isCellularAvailable()
+            if (!isCellularAvailable()
                     && isSatelliteAllowed()
                     && (isSatelliteViaOemAvailable() || isSatelliteViaCarrierAvailable())
                     && shouldTrackCall(mEmergencyConnection.getState())) {
-                logd("handleTimeoutEvent: Sent EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer");
+                plogd("handleTimeoutEvent: Sent EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer");
                 Bundle extras = createExtraBundleForEventDisplayEmergencyMessage();
                 mEmergencyConnection.sendConnectionEvent(
                         TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE, extras);
                 isDialerNotified = true;
 
             }
-            logd("handleTimeoutEvent: isImsRegistered=" + isImsRegistered()
+            plogd("handleTimeoutEvent: isImsRegistered=" + isImsRegistered()
                     + ", isCellularAvailable=" + isCellularAvailable()
                     + ", isSatelliteAllowed=" + isSatelliteAllowed()
                     + ", shouldTrackCall=" + shouldTrackCall(mEmergencyConnection.getState()));
@@ -325,6 +346,7 @@
 
     private void handleEmergencyCallConnectionStateChangedEvent(
             @NonNull Pair<String, Integer> arg) {
+        mSatelliteController.setLastEmergencyCallTime();
         if (mEmergencyConnection == null) {
             // Either the call was not created or the timer already timed out.
             return;
@@ -333,7 +355,7 @@
         String callId = arg.first;
         int state = arg.second;
         if (!mEmergencyConnection.getTelecomCallId().equals(callId)) {
-            loge("handleEmergencyCallConnectionStateChangedEvent: unexpected state changed event "
+            ploge("handleEmergencyCallConnectionStateChangedEvent: unexpected state changed event "
                     + ", mEmergencyConnection=" + mEmergencyConnection + ", callId=" + callId
                     + ", state=" + state);
             /*
@@ -402,7 +424,7 @@
             imsManager.addRegistrationCallback(
                     getOrCreateImsRegistrationCallback(phone.getPhoneId()), this::post);
         } catch (ImsException ex) {
-            loge("registerForImsRegistrationStateChanged: ex=" + ex);
+            ploge("registerForImsRegistrationStateChanged: ex=" + ex);
         }
     }
 
@@ -423,7 +445,7 @@
             imsManager.removeRegistrationListener(
                     mImsRegistrationCallbacks.get(phone.getPhoneId()));
         } else {
-            loge("Phone ID=" + phone.getPhoneId() + " was not registered with ImsManager");
+            ploge("Phone ID=" + phone.getPhoneId() + " was not registered with ImsManager");
         }
     }
 
@@ -432,12 +454,43 @@
             ServiceState serviceState = phone.getServiceState();
             if (serviceState != null) {
                 int state = serviceState.getState();
-                if ((state == STATE_IN_SERVICE || state == STATE_EMERGENCY_ONLY)
-                        && !serviceState.isUsingNonTerrestrialNetwork()) {
+                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;
     }
 
@@ -472,9 +525,10 @@
     }
 
     private synchronized void handleStateChangedEventForHysteresisTimer() {
-        if (!isImsRegistered() && !isCellularAvailable()) {
+        if (!isCellularAvailable()) {
             startTimer();
         } else {
+            logv("handleStateChangedEventForHysteresisTimer stopTimer");
             stopTimer();
         }
     }
@@ -487,6 +541,7 @@
             sendMessageDelayed(obtainMessage(EVENT_TIME_OUT), mTimeoutMillis);
             mCountOfTimerStarted++;
             mIsTimerTimedOut = false;
+            logd("startTimer mCountOfTimerStarted=" + mCountOfTimerStarted);
         }
     }
 
@@ -504,7 +559,18 @@
         }
     }
 
-    private static long getEmergencyCallWaitForConnectionTimeoutMillis(@NonNull Context context) {
+    private void selectEmergencyCallWaitForConnectionTimeoutDuration() {
+        if (mSatelliteController.isSatelliteEmergencyMessagingSupportedViaCarrier()) {
+            mTimeoutMillis =
+                    mSatelliteController.getCarrierEmergencyCallWaitForConnectionTimeoutMillis();
+        } else {
+            mTimeoutMillis = mOemEnabledTimeoutMillis;
+        }
+        plogd("mTimeoutMillis = " + mTimeoutMillis);
+    }
+
+    private static long getOemEnabledEmergencyCallWaitForConnectionTimeoutMillis(
+            @NonNull Context context) {
         return context.getResources().getInteger(
                 R.integer.config_emergency_call_wait_for_connection_timeout_millis);
     }
@@ -608,21 +674,34 @@
             packageName = defaultSmsAppComponent.getPackageName();
             className = defaultSmsAppComponent.getClassName();
         }
-        logd("EVENT_DISPLAY_EMERGENCY_MESSAGE: handoverType=" + handoverType + ", packageName="
+        plogd("EVENT_DISPLAY_EMERGENCY_MESSAGE: handoverType=" + handoverType + ", packageName="
                 + packageName + ", className=" + className + ", action=" + action);
 
         Bundle result = new Bundle();
         result.putInt(EXTRA_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE, handoverType);
         if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
             result.putParcelable(EXTRA_EMERGENCY_CALL_TO_SATELLITE_LAUNCH_INTENT,
-                    createHandoverAppLaunchPendingIntent(packageName, className, action));
+                    createHandoverAppLaunchPendingIntent(
+                            handoverType, packageName, className, action));
         }
         return result;
     }
 
-    @NonNull private PendingIntent createHandoverAppLaunchPendingIntent(
+    @NonNull private PendingIntent createHandoverAppLaunchPendingIntent(int handoverType,
             @NonNull String packageName, @NonNull String className, @Nullable String action) {
-        Intent intent = new Intent(action);
+        Intent intent;
+        if (handoverType == EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911) {
+            String emergencyNumber = "911";
+            if (mEmergencyConnection != null) {
+                emergencyNumber = mEmergencyConnection.getAddress().getSchemeSpecificPart();
+            }
+            plogd("emergencyNumber=" + emergencyNumber);
+
+            Uri uri = Uri.parse("smsto:" + emergencyNumber);
+            intent = new Intent(Intent.ACTION_SENDTO, uri);
+        } else {
+            intent = new Intent(action);
+        }
         intent.setComponent(new ComponentName(packageName, className));
         return PendingIntent.getActivity(mContext, 0, intent,
                 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
@@ -648,15 +727,17 @@
 
     private void handleCmdSendEventDisplayEmergencyMessageForcefully(
             @NonNull Connection connection) {
-        logd("Sent EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer forcefully.");
+        plogd("Sent EVENT_DISPLAY_EMERGENCY_MESSAGE to Dialer forcefully.");
+        mEmergencyConnection = connection;
         Bundle extras = createExtraBundleForEventDisplayEmergencyMessage();
         connection.sendConnectionEvent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE, extras);
+        mEmergencyConnection = null;
     }
 
     private boolean isMultiSim() {
         TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
         if (telephonyManager == null) {
-            loge("isMultiSim: telephonyManager is null");
+            ploge("isMultiSim: telephonyManager is null");
             return false;
         }
         return telephonyManager.isMultiSimEnabled();
@@ -673,7 +754,7 @@
     private void requestIsSatelliteAllowedForCurrentLocation() {
         synchronized (mLock) {
             if (mCheckingAccessRestrictionInProgress) {
-                logd("requestIsSatelliteCommunicationAllowedForCurrentLocation was already sent");
+                plogd("requestIsSatelliteCommunicationAllowedForCurrentLocation was already sent");
                 return;
             }
             mCheckingAccessRestrictionInProgress = true;
@@ -683,14 +764,14 @@
                 new OutcomeReceiver<>() {
                     @Override
                     public void onResult(Boolean result) {
-                        logd("requestIsSatelliteAllowedForCurrentLocation: result=" + result);
+                        plogd("requestIsSatelliteAllowedForCurrentLocation: result=" + result);
                         sendMessage(obtainMessage(
                                 EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT, result));
                     }
 
                     @Override
                     public void onError(SatelliteManager.SatelliteException ex) {
-                        logd("requestIsSatelliteAllowedForCurrentLocation: onError, ex=" + ex);
+                        plogd("requestIsSatelliteAllowedForCurrentLocation: onError, ex=" + ex);
                         sendMessage(obtainMessage(
                                 EVENT_SATELLITE_ACCESS_RESTRICTION_CHECKING_RESULT, false));
                     }
@@ -711,6 +792,23 @@
                 || SystemProperties.getBoolean(BOOT_ALLOW_MOCK_MODEM_PROPERTY, false));
     }
 
+    private boolean isSatelliteSupported() {
+        if (mSatelliteController.isSatelliteEmergencyMessagingSupportedViaCarrier()) return true;
+        if (mSatelliteController.isSatelliteSupportedViaOem() && isSatelliteViaOemProvisioned()) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isSatelliteViaOemProvisioned() {
+        Boolean provisioned = mSatelliteController.isSatelliteViaOemProvisioned();
+        return (provisioned != null) && provisioned;
+    }
+
+    private static void logv(@NonNull String log) {
+        Rlog.v(TAG, log);
+    }
+
     private static void logd(@NonNull String log) {
         Rlog.d(TAG, log);
     }
@@ -718,4 +816,31 @@
     private static void loge(@NonNull String log) {
         Rlog.e(TAG, log);
     }
+
+    private boolean isSatellitePersistentLoggingEnabled(
+            @NonNull Context context) {
+        if (satellitePersistentLogging()) {
+            return true;
+        }
+        try {
+            return context.getResources().getBoolean(
+                    R.bool.config_dropboxmanager_persistent_logging_enabled);
+        } catch (RuntimeException e) {
+            return false;
+        }
+    }
+
+    private void plogd(@NonNull String log) {
+        Rlog.d(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.debug(TAG, log);
+        }
+    }
+
+    private void ploge(@NonNull String log) {
+        Rlog.e(TAG, log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.error(TAG, log);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
index 0e6f706..d33fd73 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteServiceUtils.java
@@ -16,9 +16,6 @@
 
 package com.android.internal.telephony.satellite;
 
-import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE;
-import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE;
-
 import static java.util.stream.Collectors.joining;
 
 import android.annotation.NonNull;
@@ -43,6 +40,7 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.subscription.SubscriptionManagerService;
+import com.android.internal.telephony.util.TelephonyUtils;
 
 import java.util.Arrays;
 import java.util.HashMap;
@@ -302,19 +300,23 @@
         }
 
         for (String plmn : supportedServicesBundle.keySet()) {
-            Set<Integer> supportedServicesSet = new HashSet<>();
-            for (int serviceType : supportedServicesBundle.getIntArray(plmn)) {
-                if (isServiceTypeValid(serviceType)) {
-                    supportedServicesSet.add(serviceType);
-                } else {
-                    loge("parseSupportedSatelliteServices: invalid service type=" + serviceType
-                            + " for plmn=" + plmn);
+            if (TelephonyUtils.isValidPlmn(plmn)) {
+                Set<Integer> supportedServicesSet = new HashSet<>();
+                for (int serviceType : supportedServicesBundle.getIntArray(plmn)) {
+                    if (TelephonyUtils.isValidService(serviceType)) {
+                        supportedServicesSet.add(serviceType);
+                    } else {
+                        loge("parseSupportedSatelliteServices: invalid service type=" + serviceType
+                                + " for plmn=" + plmn);
+                    }
                 }
+                logd("parseSupportedSatelliteServices: plmn=" + plmn + ", supportedServicesSet="
+                        + supportedServicesSet.stream().map(String::valueOf).collect(
+                        joining(",")));
+                supportedServicesMap.put(plmn, supportedServicesSet);
+            } else {
+                loge("parseSupportedSatelliteServices: invalid plmn=" + plmn);
             }
-            logd("parseSupportedSatelliteServices: plmn=" + plmn + ", supportedServicesSet="
-                    + supportedServicesSet.stream().map(String::valueOf).collect(
-                            joining(",")));
-            supportedServicesMap.put(plmn, supportedServicesSet);
         }
         return supportedServicesMap;
     }
@@ -330,8 +332,39 @@
         return mergedStrSet.stream().toList();
     }
 
-    private static boolean isServiceTypeValid(int serviceType) {
-        return (serviceType >= FIRST_SERVICE_TYPE && serviceType <= LAST_SERVICE_TYPE);
+    /**
+     * Merge three string lists into one such that the result list does not have any duplicate
+     * items.
+     */
+    @NonNull
+    public static List<String> mergeStrLists(List<String> strList1, List<String> strList2,
+            List<String> strList3) {
+        Set<String> mergedStrSet = new HashSet<>();
+        mergedStrSet.addAll(strList1);
+        mergedStrSet.addAll(strList2);
+        mergedStrSet.addAll(strList3);
+        return mergedStrSet.stream().toList();
+    }
+
+    /**
+     * Check if the datagramType is the sos message (DATAGRAM_TYPE_SOS_MESSAGE,
+     * DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
+     * DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED) or not
+     */
+    public static boolean isSosMessage(int datagramType) {
+        return datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE
+                || datagramType == SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP
+                || datagramType == SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED;
+    }
+
+    /**
+     * Check if the datagramType is the last sos message
+     * (DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP or
+     * DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED) or not
+     */
+    public static boolean isLastSosMessage(int datagramType) {
+        return datagramType == SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP
+                || datagramType == SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED;
     }
 
     /**
@@ -349,7 +382,7 @@
      * @return phone associated with {@code subId} or {@code null} if it doesn't exist.
      */
     public static @Nullable Phone getPhone(int subId) {
-        return PhoneFactory.getPhone(subId);
+        return PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
     }
 
     private static void logd(@NonNull String log) {
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
index 5c79c28..dde10a0 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteSessionController.java
@@ -39,6 +39,8 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.telephony.DropBoxManagerLoggerBackend;
+import android.telephony.PersistentLogger;
 import android.telephony.Rlog;
 import android.telephony.satellite.ISatelliteModemStateCallback;
 import android.telephony.satellite.SatelliteManager;
@@ -51,6 +53,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.ExponentialBackoff;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 
@@ -101,6 +104,8 @@
     private static final int EVENT_SATELLITE_MODEM_STATE_CHANGED = 4;
     private static final int EVENT_DISABLE_CELLULAR_MODEM_WHILE_SATELLITE_MODE_IS_ON_DONE = 5;
     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 long REBIND_INITIAL_DELAY = 2 * 1000; // 2 seconds
     private static final long REBIND_MAXIMUM_DELAY = 64 * 1000; // 1 minute
@@ -120,6 +125,8 @@
     @NonNull private final SatelliteModemInterface mSatelliteModemInterface;
     @NonNull private final UnavailableState mUnavailableState = new UnavailableState();
     @NonNull private final PowerOffState mPowerOffState = new PowerOffState();
+    @NonNull private final EnablingState mEnablingState = new EnablingState();
+    @NonNull private final DisablingState mDisablingState = new DisablingState();
     @NonNull private final IdleState mIdleState = new IdleState();
     @NonNull private final TransferringState mTransferringState = new TransferringState();
     @NonNull private final ListeningState mListeningState = new ListeningState();
@@ -138,6 +145,8 @@
     @NonNull private boolean mIsDisableCellularModemInProgress = false;
     @NonNull private final SatelliteController mSatelliteController;
     @NonNull private final DatagramController mDatagramController;
+    @Nullable private PersistentLogger mPersistentLogger = null;
+
 
     /**
      * @return The singleton instance of SatelliteSessionController.
@@ -154,20 +163,22 @@
      *
      * @param context The Context for the SatelliteSessionController.
      * @param looper The looper associated with the handler of this class.
+     * @param featureFlags The telephony feature flags.
      * @param isSatelliteSupported Whether satellite is supported on the device.
      * @return The singleton instance of SatelliteSessionController.
      */
     public static SatelliteSessionController make(
-            @NonNull Context context, @NonNull Looper looper, boolean isSatelliteSupported) {
-        if (sInstance == null) {
-            sInstance = new SatelliteSessionController(context, looper, isSatelliteSupported,
+            @NonNull Context context,
+            @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags,
+            boolean isSatelliteSupported) {
+        if (sInstance == null || isSatelliteSupported != sInstance.mIsSatelliteSupported) {
+            sInstance = new SatelliteSessionController(
+                    context,
+                    looper,
+                    featureFlags,
+                    isSatelliteSupported,
                     SatelliteModemInterface.getInstance());
-        } else {
-            if (isSatelliteSupported != sInstance.mIsSatelliteSupported) {
-                Rlog.e(TAG, "New satellite support state " + isSatelliteSupported
-                        + " is different from existing state " + sInstance.mIsSatelliteSupported
-                        + ". Ignore the new state.");
-            }
         }
         return sInstance;
     }
@@ -177,15 +188,22 @@
      *
      * @param context The Context for the SatelliteSessionController.
      * @param looper The looper associated with the handler of this class.
+     * @param featureFlags The telephony feature flags.
      * @param isSatelliteSupported Whether satellite is supported on the device.
      * @param satelliteModemInterface The singleton of SatelliteModemInterface.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     protected SatelliteSessionController(@NonNull Context context, @NonNull Looper looper,
+            @NonNull FeatureFlags featureFlags,
             boolean isSatelliteSupported,
             @NonNull SatelliteModemInterface satelliteModemInterface) {
         super(TAG, looper);
 
+        if (isSatellitePersistentLoggingEnabled(context, featureFlags)) {
+            mPersistentLogger = new PersistentLogger(
+                    DropBoxManagerLoggerBackend.getInstance(context));
+        }
+
         mContext = context;
         mSatelliteModemInterface = satelliteModemInterface;
         mSatelliteController = SatelliteController.getInstance();
@@ -218,9 +236,11 @@
 
         addState(mUnavailableState);
         addState(mPowerOffState);
+        addState(mEnablingState);
+        addState(mDisablingState);
         addState(mIdleState);
         addState(mTransferringState);
-        addState(mListeningState, mTransferringState);
+        addState(mListeningState);
         addState(mNotConnectedState);
         addState(mConnectedState);
         setInitialState(isSatelliteSupported);
@@ -258,6 +278,26 @@
 
     /**
      * {@link SatelliteController} uses this function to notify {@link SatelliteSessionController}
+     * that the satellite enablement has just started.
+     *
+     * @param enabled {@code true} means being enabled and {@code false} means being disabled.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void onSatelliteEnablementStarted(boolean enabled) {
+        sendMessage(EVENT_SATELLITE_ENABLEMENT_STARTED, enabled);
+    }
+
+    /**
+     * {@link SatelliteController} uses this function to notify {@link SatelliteSessionController}
+     * that the satellite enablement has just failed.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void onSatelliteEnablementFailed() {
+        sendMessage(EVENT_SATELLITE_ENABLEMENT_FAILED);
+    }
+
+    /**
+     * {@link SatelliteController} uses this function to notify {@link SatelliteSessionController}
      * that the satellite modem state has changed.
      *
      * @param state The current state of the satellite modem.
@@ -278,7 +318,7 @@
             callback.onSatelliteModemStateChanged(mCurrentState);
             mListeners.put(callback.asBinder(), callback);
         } catch (RemoteException ex) {
-            loge("registerForSatelliteModemStateChanged: Got RemoteException ex=" + ex);
+            ploge("registerForSatelliteModemStateChanged: Got RemoteException ex=" + ex);
         }
     }
 
@@ -304,11 +344,11 @@
      */
     boolean setSatelliteListeningTimeoutDuration(long timeoutMillis) {
         if (!isMockModemAllowed()) {
-            loge("Updating listening timeout duration is not allowed");
+            ploge("Updating listening timeout duration is not allowed");
             return false;
         }
 
-        logd("setSatelliteListeningTimeoutDuration: timeoutMillis=" + timeoutMillis);
+        plogd("setSatelliteListeningTimeoutDuration: timeoutMillis=" + timeoutMillis);
         if (timeoutMillis == 0) {
             mSatelliteStayAtListeningFromSendingMillis =
                     getSatelliteStayAtListeningFromSendingMillis();
@@ -334,12 +374,12 @@
      */
     boolean setSatelliteGatewayServicePackageName(@Nullable String servicePackageName) {
         if (!isMockModemAllowed()) {
-            loge("setSatelliteGatewayServicePackageName: modifying satellite gateway service "
+            ploge("setSatelliteGatewayServicePackageName: modifying satellite gateway service "
                     + "package name is not allowed");
             return false;
         }
 
-        logd("setSatelliteGatewayServicePackageName: config_satellite_gateway_service_package is "
+        plogd("setSatelliteGatewayServicePackageName: config_satellite_gateway_service_package is "
                 + "updated, new packageName=" + servicePackageName);
 
         if (servicePackageName == null || servicePackageName.equals("null")) {
@@ -372,6 +412,16 @@
         mIsDemoMode = isDemoMode;
     }
 
+    /**
+     * Get whether state machine is in enabling state.
+     *
+     * @return {@code true} if state machine is in enabling state and {@code false} otherwise.
+     */
+    public boolean isInEnablingState() {
+        if (DBG) plogd("isInEnablingState: getCurrentState=" + getCurrentState());
+        return getCurrentState() == mEnablingState;
+    }
+
     private boolean isDemoMode() {
         return mIsDemoMode;
     }
@@ -390,14 +440,14 @@
     private class UnavailableState extends State {
         @Override
         public void enter() {
-            if (DBG) logd("Entering UnavailableState");
+            if (DBG) plogd("Entering UnavailableState");
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE;
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE);
         }
 
         @Override
         public boolean processMessage(Message msg) {
-            loge("UnavailableState: receive msg " + getWhatToString(msg.what) + " unexpectedly");
+            ploge("UnavailableState: receive msg " + getWhatToString(msg.what) + " unexpectedly");
             return HANDLED;
         }
     }
@@ -405,7 +455,7 @@
     private class PowerOffState extends State {
         @Override
         public void enter() {
-            if (DBG) logd("Entering PowerOffState");
+            if (DBG) plogd("Entering PowerOffState");
 
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_OFF;
             mIsSendingTriggeredDuringTransferringState.set(false);
@@ -414,23 +464,65 @@
             }
             unbindService();
             stopNbIotInactivityTimer();
+            DemoSimulator.getInstance().onSatelliteModeOff();
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_OFF);
         }
 
         @Override
         public void exit() {
-            if (DBG) logd("Exiting PowerOffState");
-            logd("Attempting to bind to SatelliteGatewayService.");
+            if (DBG) plogd("Exiting PowerOffState");
+            plogd("Attempting to bind to SatelliteGatewayService.");
             bindService();
         }
 
         @Override
         public boolean processMessage(Message msg) {
-            if (DBG) log("PowerOffState: processing " + getWhatToString(msg.what));
+            if (DBG) plogd("PowerOffState: processing " + getWhatToString(msg.what));
+            switch (msg.what) {
+                case EVENT_SATELLITE_ENABLEMENT_STARTED:
+                    handleSatelliteEnablementStarted((boolean) msg.obj);
+                    break;
+            }
+            // Ignore all unexpected events.
+            return HANDLED;
+        }
+
+        private void handleSatelliteEnablementStarted(boolean enabled) {
+            if (enabled) {
+                transitionTo(mEnablingState);
+            } else {
+                plogw("Unexpected satellite disablement started in PowerOff state");
+            }
+        }
+    }
+
+    private class EnablingState extends State {
+        @Override
+        public void enter() {
+            if (DBG) plogd("Entering EnablingState");
+
+            mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_ENABLING_SATELLITE;
+            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_ENABLING_SATELLITE);
+        }
+
+        @Override
+        public void exit() {
+            if (DBG) plogd("Exiting EnablingState");
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) plogd("EnablingState: processing " + getWhatToString(msg.what));
             switch (msg.what) {
                 case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
                     handleSatelliteEnabledStateChanged((boolean) msg.obj);
                     break;
+                case EVENT_SATELLITE_ENABLEMENT_FAILED:
+                    transitionTo(mPowerOffState);
+                    break;
+                case EVENT_SATELLITE_MODEM_STATE_CHANGED:
+                    deferMessage(msg);
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -443,6 +535,51 @@
                 } else {
                     transitionTo(mIdleState);
                 }
+                DemoSimulator.getInstance().onSatelliteModeOn();
+            } else {
+                /*
+                 * During the state transition from ENABLING to NOT_CONNECTED, modem might be
+                 * reset. In such cases, we need to remove all deferred
+                 * EVENT_SATELLITE_MODEM_STATE_CHANGED events so that they will not mess up our
+                 * state machine later.
+                 */
+                removeDeferredMessages(EVENT_SATELLITE_MODEM_STATE_CHANGED);
+                transitionTo(mPowerOffState);
+            }
+        }
+    }
+
+    private class DisablingState extends State {
+        @Override
+        public void enter() {
+            if (DBG) plogd("Entering DisablingState");
+
+            mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_DISABLING_SATELLITE;
+            notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_DISABLING_SATELLITE);
+        }
+
+        @Override
+        public void exit() {
+            if (DBG) plogd("Exiting DisablingState");
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (DBG) plogd("DisablingState: processing " + getWhatToString(msg.what));
+            switch (msg.what) {
+                case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
+                    handleSatelliteEnabledStateChanged((boolean) msg.obj);
+                    break;
+            }
+            // Ignore all unexpected events.
+            return HANDLED;
+        }
+
+        private void handleSatelliteEnabledStateChanged(boolean on) {
+            if (on) {
+                plogw("Unexpected power on event while disabling satellite");
+            } else {
+                transitionTo(mPowerOffState);
             }
         }
     }
@@ -450,7 +587,7 @@
     private class IdleState extends State {
         @Override
         public void enter() {
-            if (DBG) logd("Entering IdleState");
+            if (DBG) plogd("Entering IdleState");
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_IDLE;
             mIsSendingTriggeredDuringTransferringState.set(false);
             stopNbIotInactivityTimer();
@@ -461,7 +598,7 @@
 
         @Override
         public boolean processMessage(Message msg) {
-            if (DBG) log("IdleState: processing " + getWhatToString(msg.what));
+            if (DBG) plogd("IdleState: processing " + getWhatToString(msg.what));
             switch (msg.what) {
                 case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED:
                     handleEventDatagramTransferStateChanged((DatagramTransferState) msg.obj);
@@ -473,6 +610,9 @@
                     handleEventDisableCellularModemWhileSatelliteModeIsOnDone(
                             (AsyncResult) msg.obj);
                     break;
+                case EVENT_SATELLITE_ENABLEMENT_STARTED:
+                    handleSatelliteEnablementStarted((boolean) msg.obj);
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -484,7 +624,7 @@
                     || (datagramTransferState.receiveState
                     == SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING)) {
                 if (mSatelliteController.isSatelliteAttachRequired()) {
-                    loge("Unexpected transferring state received for NB-IOT NTN");
+                    ploge("Unexpected transferring state received for NB-IOT NTN");
                 } else {
                     transitionTo(mTransferringState);
                 }
@@ -495,7 +635,7 @@
                 if (mSatelliteController.isSatelliteAttachRequired()) {
                     disableCellularModemWhileSatelliteModeIsOn();
                 } else {
-                    loge("Unexpected transferring state received for non-NB-IOT NTN");
+                    ploge("Unexpected transferring state received for non-NB-IOT NTN");
                 }
             }
         }
@@ -511,7 +651,7 @@
                     }
                     mIsDisableCellularModemInProgress = false;
                 } else {
-                    loge("DisableCellularModemWhileSatelliteModeIsOn is not in progress");
+                    ploge("DisableCellularModemWhileSatelliteModeIsOn is not in progress");
                 }
             }
         }
@@ -519,7 +659,7 @@
         private void disableCellularModemWhileSatelliteModeIsOn() {
             synchronized (mLock) {
                 if (mIsDisableCellularModemInProgress) {
-                    logd("Cellular scanning is already being disabled");
+                    plogd("Cellular scanning is already being disabled");
                     return;
                 }
 
@@ -533,7 +673,7 @@
 
         @Override
         public void exit() {
-            if (DBG) logd("Exiting IdleState");
+            if (DBG) plogd("Exiting IdleState");
             if (!mSatelliteController.isSatelliteAttachRequired()) {
                 // Disable cellular modem scanning
                 mSatelliteModemInterface.enableCellularModemWhileSatelliteModeIsOn(false, null);
@@ -544,7 +684,7 @@
     private class TransferringState extends State {
         @Override
         public void enter() {
-            if (DBG) logd("Entering TransferringState");
+            if (DBG) plogd("Entering TransferringState");
             stopNbIotInactivityTimer();
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING;
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
@@ -552,7 +692,7 @@
 
         @Override
         public boolean processMessage(Message msg) {
-            if (DBG) log("TransferringState: processing " + getWhatToString(msg.what));
+            if (DBG) plogd("TransferringState: processing " + getWhatToString(msg.what));
             switch (msg.what) {
                 case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED:
                     handleEventDatagramTransferStateChanged((DatagramTransferState) msg.obj);
@@ -563,6 +703,9 @@
                 case EVENT_SATELLITE_MODEM_STATE_CHANGED:
                     handleEventSatelliteModemStateChange(msg.arg1);
                     break;
+                case EVENT_SATELLITE_ENABLEMENT_STARTED:
+                    handleSatelliteEnablementStarted((boolean) msg.obj);
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -600,7 +743,7 @@
     private class ListeningState extends State {
         @Override
         public void enter() {
-            if (DBG) logd("Entering ListeningState");
+            if (DBG) plogd("Entering ListeningState");
 
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_LISTENING;
             long timeoutMillis = updateListeningMode(true);
@@ -611,7 +754,7 @@
 
         @Override
         public void exit() {
-            if (DBG) logd("Exiting ListeningState");
+            if (DBG) plogd("Exiting ListeningState");
 
             removeMessages(EVENT_LISTENING_TIMER_TIMEOUT);
             updateListeningMode(false);
@@ -619,7 +762,7 @@
 
         @Override
         public boolean processMessage(Message msg) {
-            if (DBG) log("ListeningState: processing " + getWhatToString(msg.what));
+            if (DBG) plogd("ListeningState: processing " + getWhatToString(msg.what));
             switch (msg.what) {
                 case EVENT_LISTENING_TIMER_TIMEOUT:
                     transitionTo(mIdleState);
@@ -630,6 +773,9 @@
                 case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
                     handleSatelliteEnabledStateChanged(!(boolean) msg.obj, "ListeningState");
                     break;
+                case EVENT_SATELLITE_ENABLEMENT_STARTED:
+                    handleSatelliteEnablementStarted((boolean) msg.obj);
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -660,7 +806,7 @@
     private class NotConnectedState extends State {
         @Override
         public void enter() {
-            if (DBG) logd("Entering NotConnectedState");
+            if (DBG) plogd("Entering NotConnectedState");
 
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED;
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
@@ -669,12 +815,12 @@
 
         @Override
         public void exit() {
-            if (DBG) logd("Exiting NotConnectedState");
+            if (DBG) plogd("Exiting NotConnectedState");
         }
 
         @Override
         public boolean processMessage(Message msg) {
-            if (DBG) log("NotConnectedState: processing " + getWhatToString(msg.what));
+            if (DBG) plogd("NotConnectedState: processing " + getWhatToString(msg.what));
             switch (msg.what) {
                 case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
                     handleSatelliteEnabledStateChanged(
@@ -689,6 +835,9 @@
                 case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED:
                     handleEventDatagramTransferStateChanged((DatagramTransferState) msg.obj);
                     break;
+                case EVENT_SATELLITE_ENABLEMENT_STARTED:
+                    handleSatelliteEnablementStarted((boolean) msg.obj);
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -722,7 +871,7 @@
     private class ConnectedState extends State {
         @Override
         public void enter() {
-            if (DBG) logd("Entering ConnectedState");
+            if (DBG) plogd("Entering ConnectedState");
 
             mCurrentState = SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED;
             notifyStateChangedEvent(SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
@@ -731,12 +880,12 @@
 
         @Override
         public void exit() {
-            if (DBG) logd("Exiting ConnectedState");
+            if (DBG) plogd("Exiting ConnectedState");
         }
 
         @Override
         public boolean processMessage(Message msg) {
-            if (DBG) log("ConnectedState: processing " + getWhatToString(msg.what));
+            if (DBG) plogd("ConnectedState: processing " + getWhatToString(msg.what));
             switch (msg.what) {
                 case EVENT_SATELLITE_ENABLED_STATE_CHANGED:
                     handleSatelliteEnabledStateChanged(
@@ -751,6 +900,9 @@
                 case EVENT_DATAGRAM_TRANSFER_STATE_CHANGED:
                     handleEventDatagramTransferStateChanged((DatagramTransferState) msg.obj);
                     break;
+                case EVENT_SATELLITE_ENABLEMENT_STARTED:
+                    handleSatelliteEnablementStarted((boolean) msg.obj);
+                    break;
             }
             // Ignore all unexpected events.
             return HANDLED;
@@ -797,6 +949,12 @@
             case EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT:
                 whatString = "EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT";
                 break;
+            case EVENT_SATELLITE_ENABLEMENT_STARTED:
+                whatString = "EVENT_SATELLITE_ENABLEMENT_STARTED";
+                break;
+            case EVENT_SATELLITE_ENABLEMENT_FAILED:
+                whatString = "EVENT_SATELLITE_ENABLEMENT_FAILED";
+                break;
             default:
                 whatString = "UNKNOWN EVENT " + what;
         }
@@ -819,7 +977,7 @@
             try {
                 listener.onSatelliteModemStateChanged(state);
             } catch (RemoteException e) {
-                logd("notifyStateChangedEvent RemoteException: " + e);
+                plogd("notifyStateChangedEvent RemoteException: " + e);
                 toBeRemoved.add(listener);
             }
         });
@@ -833,7 +991,7 @@
         if (off) {
             transitionTo(mPowerOffState);
         } else {
-            loge(caller + ": Unexpected satellite radio powered-on state changed event");
+            ploge(caller + ": Unexpected satellite radio powered-on state changed event");
         }
     }
 
@@ -866,7 +1024,7 @@
 
         String packageName = getSatelliteGatewayPackageName();
         if (TextUtils.isEmpty(packageName)) {
-            loge("Unable to bind to the satellite gateway service because the package is"
+            ploge("Unable to bind to the satellite gateway service because the package is"
                     + " undefined.");
             // Since the package name comes from static device configs, stop retry because
             // rebind will continue to fail without a valid package name.
@@ -884,13 +1042,13 @@
             boolean success = mContext.bindService(
                     intent, mSatelliteGatewayServiceConnection, Context.BIND_AUTO_CREATE);
             if (success) {
-                logd("Successfully bound to the satellite gateway service.");
+                plogd("Successfully bound to the satellite gateway service.");
             } else {
                 synchronized (mLock) {
                     mIsBinding = false;
                 }
                 mExponentialBackoff.notifyFailed();
-                loge("Error binding to the satellite gateway service. Retrying in "
+                ploge("Error binding to the satellite gateway service. Retrying in "
                         + mExponentialBackoff.getCurrentDelay() + " ms.");
             }
         } catch (Exception e) {
@@ -898,13 +1056,13 @@
                 mIsBinding = false;
             }
             mExponentialBackoff.notifyFailed();
-            loge("Exception binding to the satellite gateway service. Retrying in "
+            ploge("Exception binding to the satellite gateway service. Retrying in "
                     + mExponentialBackoff.getCurrentDelay() + " ms. Exception: " + e);
         }
     }
 
     private void unbindService() {
-        logd("unbindService");
+        plogd("unbindService");
         mExponentialBackoff.stop();
         mSatelliteGatewayService = null;
         synchronized (mLock) {
@@ -919,7 +1077,7 @@
     private class SatelliteGatewayServiceConnection implements ServiceConnection {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
-            logd("onServiceConnected: ComponentName=" + name);
+            plogd("onServiceConnected: ComponentName=" + name);
             synchronized (mLock) {
                 mIsBound = true;
                 mIsBinding = false;
@@ -930,7 +1088,7 @@
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
-            loge("onServiceDisconnected: Waiting for reconnect.");
+            ploge("onServiceDisconnected: Waiting for reconnect.");
             synchronized (mLock) {
                 mIsBinding = false;
                 mIsBound = false;
@@ -940,7 +1098,7 @@
 
         @Override
         public void onBindingDied(ComponentName name) {
-            loge("onBindingDied: Unbinding and rebinding service.");
+            ploge("onBindingDied: Unbinding and rebinding service.");
             synchronized (mLock) {
                 mIsBound = false;
                 mIsBinding = false;
@@ -950,6 +1108,12 @@
         }
     }
 
+    private void handleSatelliteEnablementStarted(boolean enabled) {
+        if (!enabled) {
+            transitionTo(mDisablingState);
+        }
+    }
+
     private boolean isMockModemAllowed() {
         return (DEBUG || SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false));
     }
@@ -973,8 +1137,13 @@
     }
 
     private long getSatelliteNbIotInactivityTimeoutMillis() {
-        return mContext.getResources().getInteger(
-                R.integer.config_satellite_nb_iot_inactivity_timeout_millis);
+        if (isDemoMode()) {
+            return mContext.getResources().getInteger(
+                    R.integer.config_satellite_demo_mode_nb_iot_inactivity_timeout_millis);
+        } else {
+            return mContext.getResources().getInteger(
+                    R.integer.config_satellite_nb_iot_inactivity_timeout_millis);
+        }
     }
 
     private void restartNbIotInactivityTimer() {
@@ -984,7 +1153,7 @@
 
     private void startNbIotInactivityTimer() {
         if (isNbIotInactivityTimerStarted()) {
-            logd("NB IOT inactivity timer is already started");
+            plogd("NB IOT inactivity timer is already started");
             return;
         }
 
@@ -1004,4 +1173,38 @@
     private boolean isNbIotInactivityTimerStarted() {
         return hasMessages(EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT);
     }
+
+    private boolean isSatellitePersistentLoggingEnabled(
+            @NonNull Context context, @NonNull FeatureFlags featureFlags) {
+        if (featureFlags.satellitePersistentLogging()) {
+            return true;
+        }
+        try {
+            return context.getResources().getBoolean(
+                    R.bool.config_dropboxmanager_persistent_logging_enabled);
+        } catch (RuntimeException e) {
+            return false;
+        }
+    }
+
+    private void plogd(@NonNull String log) {
+        logd(log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.debug(TAG, log);
+        }
+    }
+
+    private void plogw(@NonNull String log) {
+        logw(log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.warn(TAG, log);
+        }
+    }
+
+    private void ploge(@NonNull String log) {
+        loge(log);
+        if (mPersistentLogger != null) {
+            mPersistentLogger.error(TAG, log);
+        }
+    }
 }
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java
new file mode 100644
index 0000000..13ba709
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/metrics/AccessControllerMetricsStats.java
@@ -0,0 +1,192 @@
+/*
+ * 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.satellite.metrics;
+
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
+
+import static com.android.internal.telephony.satellite.SatelliteConstants.CONFIG_DATA_SOURCE_UNKNOWN;
+
+import android.annotation.NonNull;
+import android.telephony.satellite.SatelliteManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.satellite.SatelliteConstants;
+
+import java.util.Arrays;
+import java.util.List;
+public class AccessControllerMetricsStats {
+    private static final String TAG = AccessControllerMetricsStats.class.getSimpleName();
+    private static AccessControllerMetricsStats sInstance = null;
+
+    private @SatelliteConstants.AccessControlType int mAccessControlType;
+    private long mLocationQueryTimeMillis;
+    private long mOnDeviceLookupTimeMillis;
+    private long mTotalCheckingTimeMillis;
+    private Boolean mIsAllowed;
+    private Boolean mIsEmergency;
+    private @SatelliteManager.SatelliteResult int mResultCode;
+    private String[] mCountryCodes;
+    private @SatelliteConstants.ConfigDataSource int mConfigDataSource;
+    private AccessControllerMetricsStats() {
+        initializeAccessControllerMetricsParam();
+    }
+
+    /**
+     * Returns the Singleton instance of AccessControllerMetricsStats class.
+     * If an instance of the Singleton class has not been created,
+     * it creates a new instance and returns it. Otherwise, it returns
+     * the existing instance.
+     * @return the Singleton instance of AccessControllerMetricsStats.
+     */
+    public static AccessControllerMetricsStats getInstance() {
+        if (sInstance == null) {
+            loge("create new AccessControllerMetricsStats.");
+            sInstance = new AccessControllerMetricsStats();
+        }
+        return sInstance;
+    }
+    private void initializeAccessControllerMetricsParam() {
+        mAccessControlType = SatelliteConstants.ACCESS_CONTROL_TYPE_UNKNOWN;
+        mLocationQueryTimeMillis = 0;
+        mOnDeviceLookupTimeMillis = 0;
+        mTotalCheckingTimeMillis = 0;
+        mIsAllowed = null;
+        mIsEmergency = null;
+        mResultCode = SATELLITE_RESULT_SUCCESS;
+        mCountryCodes = new String[0];
+        mConfigDataSource = CONFIG_DATA_SOURCE_UNKNOWN;
+    }
+    /**
+     * Sets the Access Control Type for current satellite enablement.
+     * @param accessControlType access control type of location query is attempted.
+     */
+    public AccessControllerMetricsStats setAccessControlType(
+            @SatelliteConstants.AccessControlType int accessControlType) {
+        mAccessControlType = accessControlType;
+        logd("setAccessControlType: access control type = " + mAccessControlType);
+        return this;
+    }
+    /**
+     * Sets the location query time for current satellite enablement.
+     * @param queryStartTime the time location query is attempted.
+     */
+    public AccessControllerMetricsStats setLocationQueryTime(long queryStartTime) {
+        mLocationQueryTimeMillis =
+                (queryStartTime > 0) ? (getCurrentTime() - queryStartTime) : 0;
+        logd("setLocationQueryTimeMillis: location query time = " + mLocationQueryTimeMillis);
+        return this;
+    }
+    /**
+     * Sets the on device lookup time for current satellite enablement.
+     * @param onDeviceLookupStartTime the time on device lookup is attempted.
+     */
+    public AccessControllerMetricsStats setOnDeviceLookupTime(long onDeviceLookupStartTime) {
+        mOnDeviceLookupTimeMillis =
+                (onDeviceLookupStartTime > 0) ? (getCurrentTime() - onDeviceLookupStartTime) : 0;
+        logd("setLocationQueryTime: on device lookup time = " + mOnDeviceLookupTimeMillis);
+        return this;
+    }
+    /**
+     * Sets the total checking time for current satellite enablement.
+     * @param queryStartTime the time location query is attempted.
+     */
+    public AccessControllerMetricsStats setTotalCheckingTime(long queryStartTime) {
+        mTotalCheckingTimeMillis =
+                (queryStartTime > 0) ? (getCurrentTime() - queryStartTime) : 0;
+        logd("setTotalCheckingTime: location query time = " + mTotalCheckingTimeMillis);
+        return this;
+    }
+    /**
+     * Sets whether the satellite communication is allowed from current location.
+     * @param isAllowed {@code true} if satellite communication is allowed from current location
+     *        {@code false} otherwise.
+     */
+    public AccessControllerMetricsStats setIsAllowed(boolean isAllowed) {
+        mIsAllowed = isAllowed;
+        logd("setIsAllowed: allowed=" + mIsAllowed);
+        return this;
+    }
+    /**
+     * Sets whether the current satellite enablement is for emergency or not.
+     * @param isEmergency {@code true} if current satellite enablement is for emergency SOS message
+     *        {@code false} otherwise.
+     */
+    public AccessControllerMetricsStats setIsEmergency(boolean isEmergency) {
+        mIsEmergency = isEmergency;
+        logd("setIsEmergency: emergency =" + mIsEmergency);
+        return this;
+    }
+    /**
+     * Sets the result code for checking whether satellite service is allowed from current
+     * location.
+     * @param result result code for checking process.
+     */
+    public AccessControllerMetricsStats setResult(@SatelliteManager.SatelliteResult int result) {
+        mResultCode = result;
+        logd("setResult: result = " + mResultCode);
+        return this;
+    }
+    /**
+     * Sets the country code for current location while attempting satellite enablement.
+     * @param countryCodes Country code the user is located in
+     */
+    public AccessControllerMetricsStats setCountryCodes(List<String> countryCodes) {
+        mCountryCodes = countryCodes.stream().toArray(String[]::new);
+        logd("setCountryCodes: country code is " + Arrays.toString(mCountryCodes));
+        return this;
+    }
+    /**
+     * Sets the config data source for checking whether satellite service is allowed from current
+     * location.
+     * @param configDatasource configuration data source.
+     */
+    public AccessControllerMetricsStats setConfigDataSource(
+            @SatelliteConstants.ConfigDataSource int configDatasource) {
+        mConfigDataSource = configDatasource;
+        logd("setConfigDataSource: config data source = " + mConfigDataSource);
+        return this;
+    }
+    /** Report the access controller metrics atoms to PersistAtomsStorage in telephony. */
+    public void reportAccessControllerMetrics() {
+        SatelliteStats.SatelliteAccessControllerParams accessControllerParams =
+                new SatelliteStats.SatelliteAccessControllerParams.Builder()
+                        .setAccessControlType(mAccessControlType)
+                        .setLocationQueryTime(mLocationQueryTimeMillis)
+                        .setOnDeviceLookupTime(mOnDeviceLookupTimeMillis)
+                        .setTotalCheckingTime(mTotalCheckingTimeMillis)
+                        .setIsAllowed(mIsAllowed)
+                        .setIsEmergency(mIsEmergency)
+                        .setResult(mResultCode)
+                        .setCountryCodes(mCountryCodes)
+                        .setConfigDatasource(mConfigDataSource)
+                        .build();
+        logd("reportAccessControllerMetrics: " + accessControllerParams.toString());
+        SatelliteStats.getInstance().onSatelliteAccessControllerMetrics(accessControllerParams);
+        initializeAccessControllerMetricsParam();
+    }
+    @VisibleForTesting
+    public long getCurrentTime() {
+        return System.currentTimeMillis();
+    }
+    private static void logd(@NonNull String log) {
+        Log.d(TAG, log);
+    }
+    private static void loge(@NonNull String log) {
+        Log.e(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteControllerStats.java b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteControllerStats.java
new file mode 100644
index 0000000..9524b75
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteControllerStats.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.satellite.metrics;
+
+import android.annotation.NonNull;
+import android.util.Log;
+
+import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.satellite.SatelliteConstants;
+
+public class CarrierRoamingSatelliteControllerStats {
+    private static final String TAG = CarrierRoamingSatelliteControllerStats.class.getSimpleName();
+    private static CarrierRoamingSatelliteControllerStats sInstance = null;
+    private static final int ADD_COUNT = 1;
+
+    private SatelliteStats mSatelliteStats;
+
+    private CarrierRoamingSatelliteControllerStats() {
+        mSatelliteStats = SatelliteStats.getInstance();
+    }
+
+    /**
+     * Returns the Singleton instance of CarrierRoamingSatelliteControllerStats class.
+     * If an instance of the Singleton class has not been created,
+     * it creates a new instance and returns it. Otherwise, it returns
+     * the existing instance.
+     * @return the Singleton instance of CarrierRoamingSatelliteControllerStats
+     */
+    public static CarrierRoamingSatelliteControllerStats getOrCreateInstance() {
+        if (sInstance == null) {
+            logd("Create new CarrierRoamingSatelliteControllerStats.");
+            sInstance = new CarrierRoamingSatelliteControllerStats();
+        }
+        return sInstance;
+    }
+
+    /** Report config data source */
+    public void reportConfigDataSource(@SatelliteConstants.ConfigDataSource int configDataSource) {
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(
+                new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                        .setConfigDataSource(configDataSource)
+                        .build());
+    }
+
+    /** Report count of entitlement status query request */
+    public void reportCountOfEntitlementStatusQueryRequest() {
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(
+                new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                        .setCountOfEntitlementStatusQueryRequest(ADD_COUNT)
+                        .build());
+    }
+
+    /** Report count of satellite config update request */
+    public void reportCountOfSatelliteConfigUpdateRequest() {
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(
+                new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                        .setCountOfSatelliteConfigUpdateRequest(ADD_COUNT)
+                        .build());
+    }
+
+    /** Report count of satellite notification displayed */
+    public void reportCountOfSatelliteNotificationDisplayed() {
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(
+                new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                        .setCountOfSatelliteNotificationDisplayed(ADD_COUNT)
+                        .build());
+    }
+
+    private static void logd(@NonNull String log) {
+        Log.d(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteSessionStats.java b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteSessionStats.java
new file mode 100644
index 0000000..771432e
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteSessionStats.java
@@ -0,0 +1,347 @@
+/*
+ * 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.satellite.metrics;
+
+import android.annotation.NonNull;
+import android.telephony.CellInfo;
+import android.telephony.CellSignalStrength;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.telephony.MccTable;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
+import com.android.internal.telephony.subscription.SubscriptionManagerService;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class CarrierRoamingSatelliteSessionStats {
+    private static final String TAG = CarrierRoamingSatelliteSessionStats.class.getSimpleName();
+    private static final SparseArray<CarrierRoamingSatelliteSessionStats>
+            sCarrierRoamingSatelliteSessionStats = new SparseArray<>();
+    @NonNull private final SubscriptionManagerService mSubscriptionManagerService;
+    private int mCarrierId;
+    private boolean mIsNtnRoamingInHomeCountry;
+    private int mCountOfIncomingSms;
+    private int mCountOfOutgoingSms;
+    private int mCountOfIncomingMms;
+    private int mCountOfOutgoingMms;
+    private long mIncomingMessageId;
+
+    private int mSessionStartTimeSec;
+    private List<Long> mConnectionStartTimeList;
+    private List<Long> mConnectionEndTimeList;
+    private List<Integer> mRsrpList;
+    private List<Integer> mRssnrList;
+
+    public CarrierRoamingSatelliteSessionStats(int subId) {
+        logd("Create new CarrierRoamingSatelliteSessionStats. subId=" + subId);
+        initializeParams();
+        mSubscriptionManagerService = SubscriptionManagerService.getInstance();
+    }
+
+    /** Gets a CarrierRoamingSatelliteSessionStats instance. */
+    public static CarrierRoamingSatelliteSessionStats getInstance(int subId) {
+        synchronized (sCarrierRoamingSatelliteSessionStats) {
+            if (sCarrierRoamingSatelliteSessionStats.get(subId) == null) {
+                sCarrierRoamingSatelliteSessionStats.put(subId,
+                        new CarrierRoamingSatelliteSessionStats(subId));
+            }
+            return sCarrierRoamingSatelliteSessionStats.get(subId);
+        }
+    }
+
+    /** Log carrier roaming satellite session start */
+    public void onSessionStart(int carrierId, Phone phone) {
+        mCarrierId = carrierId;
+        mSessionStartTimeSec = getCurrentTimeInSec();
+        mIsNtnRoamingInHomeCountry = false;
+        onConnectionStart(phone);
+    }
+
+    /** Log carrier roaming satellite connection start */
+    public void onConnectionStart(Phone phone) {
+        mConnectionStartTimeList.add(getCurrentTime());
+        updateNtnRoamingInHomeCountry(phone);
+    }
+
+    /** Log carrier roaming satellite session end */
+    public void onSessionEnd() {
+        onConnectionEnd();
+        reportMetrics();
+        mIsNtnRoamingInHomeCountry = false;
+    }
+
+    /** Log carrier roaming satellite connection end */
+    public void onConnectionEnd() {
+        mConnectionEndTimeList.add(getCurrentTime());
+    }
+
+    /** Log rsrp and rssnr when occurred the service state change with NTN is connected. */
+    public void onSignalStrength(Phone phone) {
+        CellSignalStrengthLte cellSignalStrengthLte = getCellSignalStrengthLte(phone);
+        int rsrp = cellSignalStrengthLte.getRsrp();
+        int rssnr = cellSignalStrengthLte.getRssnr();
+        if (rsrp == CellInfo.UNAVAILABLE) {
+            logd("onSignalStrength: rsrp unavailable");
+            return;
+        }
+        if (rssnr == CellInfo.UNAVAILABLE) {
+            logd("onSignalStrength: rssnr unavailable");
+            return;
+        }
+        mRsrpList.add(rsrp);
+        mRssnrList.add(rssnr);
+        logd("onSignalStrength : rsrp=" + rsrp + ", rssnr=" + rssnr);
+    }
+
+    /** Log incoming sms success case */
+    public void onIncomingSms(int subId) {
+        if (!isNtnConnected()) {
+            return;
+        }
+        mCountOfIncomingSms += 1;
+        logd("onIncomingSms: subId=" + subId + ", count=" + mCountOfIncomingSms);
+    }
+
+    /** Log outgoing sms success case */
+    public void onOutgoingSms(int subId) {
+        if (!isNtnConnected()) {
+            return;
+        }
+        mCountOfOutgoingSms += 1;
+        logd("onOutgoingSms: subId=" + subId + ", count=" + mCountOfOutgoingSms);
+    }
+
+    /** Log incoming or outgoing mms success case */
+    public void onMms(boolean isIncomingMms, long messageId) {
+        if (!isNtnConnected()) {
+            return;
+        }
+        if (isIncomingMms) {
+            mIncomingMessageId = messageId;
+            mCountOfIncomingMms += 1;
+            logd("onMms: messageId=" + messageId + ", countOfIncomingMms=" + mCountOfIncomingMms);
+        } else {
+            if (mIncomingMessageId == messageId) {
+                logd("onMms: NotifyResponse ignore it.");
+                mIncomingMessageId = 0;
+                return;
+            }
+            mCountOfOutgoingMms += 1;
+            logd("onMms: countOfOutgoingMms=" + mCountOfOutgoingMms);
+        }
+    }
+
+    private void reportMetrics() {
+        int totalSatelliteModeTimeSec = mSessionStartTimeSec > 0
+                ? getCurrentTimeInSec() - mSessionStartTimeSec : 0;
+        int numberOfSatelliteConnections = getNumberOfSatelliteConnections();
+        int avgDurationOfSatelliteConnectionSec = getAvgDurationOfSatelliteConnection(
+                numberOfSatelliteConnections);
+
+        List<Integer> connectionGapList = getSatelliteConnectionGapList(
+                numberOfSatelliteConnections);
+        int satelliteConnectionGapMinSec = 0;
+        int satelliteConnectionGapMaxSec = 0;
+        if (!connectionGapList.isEmpty()) {
+            satelliteConnectionGapMinSec = Collections.min(connectionGapList);
+            satelliteConnectionGapMaxSec = Collections.max(connectionGapList);
+        }
+
+        SatelliteStats.CarrierRoamingSatelliteSessionParams params =
+                new SatelliteStats.CarrierRoamingSatelliteSessionParams.Builder()
+                        .setCarrierId(mCarrierId)
+                        .setIsNtnRoamingInHomeCountry(mIsNtnRoamingInHomeCountry)
+                        .setTotalSatelliteModeTimeSec(totalSatelliteModeTimeSec)
+                        .setNumberOfSatelliteConnections(numberOfSatelliteConnections)
+                        .setAvgDurationOfSatelliteConnectionSec(avgDurationOfSatelliteConnectionSec)
+                        .setSatelliteConnectionGapMinSec(satelliteConnectionGapMinSec)
+                        .setSatelliteConnectionGapAvgSec(getAvg(connectionGapList))
+                        .setSatelliteConnectionGapMaxSec(satelliteConnectionGapMaxSec)
+                        .setRsrpAvg(getAvg(mRsrpList))
+                        .setRsrpMedian(getMedian(mRsrpList))
+                        .setRssnrAvg(getAvg(mRssnrList))
+                        .setRssnrMedian(getMedian(mRssnrList))
+                        .setCountOfIncomingSms(mCountOfIncomingSms)
+                        .setCountOfOutgoingSms(mCountOfOutgoingSms)
+                        .setCountOfIncomingMms(mCountOfIncomingMms)
+                        .setCountOfOutgoingMms(mCountOfOutgoingMms)
+                        .build();
+        SatelliteStats.getInstance().onCarrierRoamingSatelliteSessionMetrics(params);
+        logd("reportMetrics: " + params);
+        initializeParams();
+    }
+
+    private void initializeParams() {
+        mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+        mIsNtnRoamingInHomeCountry = false;
+        mCountOfIncomingSms = 0;
+        mCountOfOutgoingSms = 0;
+        mCountOfIncomingMms = 0;
+        mCountOfOutgoingMms = 0;
+        mIncomingMessageId = 0;
+
+        mSessionStartTimeSec = 0;
+        mConnectionStartTimeList = new ArrayList<>();
+        mConnectionEndTimeList = new ArrayList<>();
+        mRsrpList = new ArrayList<>();
+        mRssnrList = new ArrayList<>();
+        logd("initializeParams");
+    }
+
+    private CellSignalStrengthLte getCellSignalStrengthLte(Phone phone) {
+        SignalStrength signalStrength = phone.getSignalStrength();
+        List<CellSignalStrength> cellSignalStrengths = signalStrength.getCellSignalStrengths();
+        for (CellSignalStrength cellSignalStrength : cellSignalStrengths) {
+            if (cellSignalStrength instanceof CellSignalStrengthLte) {
+                return (CellSignalStrengthLte) cellSignalStrength;
+            }
+        }
+
+        return new CellSignalStrengthLte();
+    }
+
+    private int getNumberOfSatelliteConnections() {
+        return Math.min(mConnectionStartTimeList.size(), mConnectionEndTimeList.size());
+    }
+
+    private int getAvgDurationOfSatelliteConnection(int numberOfSatelliteConnections) {
+        if (numberOfSatelliteConnections == 0) {
+            return 0;
+        }
+
+        long totalConnectionsDuration = 0;
+        for (int i = 0; i < numberOfSatelliteConnections; i++) {
+            long endTime = mConnectionEndTimeList.get(i);
+            long startTime = mConnectionStartTimeList.get(i);
+            if (endTime >= startTime && startTime > 0) {
+                totalConnectionsDuration += endTime - startTime;
+            }
+        }
+
+        long avgConnectionDuration = totalConnectionsDuration / numberOfSatelliteConnections;
+        return (int) (avgConnectionDuration / 1000L);
+    }
+
+    private List<Integer> getSatelliteConnectionGapList(int numberOfSatelliteConnections) {
+        if (numberOfSatelliteConnections == 0) {
+            return new ArrayList<>();
+        }
+
+        List<Integer> connectionGapList = new ArrayList<>();
+        for (int i = 1; i < numberOfSatelliteConnections; i++) {
+            long prevConnectionEndTime = mConnectionEndTimeList.get(i - 1);
+            long currentConnectionStartTime = mConnectionStartTimeList.get(i);
+            if (currentConnectionStartTime > prevConnectionEndTime && prevConnectionEndTime > 0) {
+                connectionGapList.add((int) (
+                        (currentConnectionStartTime - prevConnectionEndTime) / 1000));
+            }
+        }
+        return connectionGapList;
+    }
+
+    private int getAvg(@NonNull List<Integer> list) {
+        if (list.isEmpty()) {
+            return 0;
+        }
+
+        int total = 0;
+        for (int num : list) {
+            total += num;
+        }
+
+        return total / list.size();
+    }
+
+    private int getMedian(@NonNull List<Integer> list) {
+        if (list.isEmpty()) {
+            return 0;
+        }
+        int size = list.size();
+        if (size == 1) {
+            return list.get(0);
+        }
+
+        Collections.sort(list);
+        return size % 2 == 0 ? (list.get(size / 2 - 1) + list.get(size / 2)) / 2
+                : list.get(size / 2);
+    }
+
+    private int getCurrentTimeInSec() {
+        return (int) (System.currentTimeMillis() / 1000);
+    }
+
+    private long getCurrentTime() {
+        return System.currentTimeMillis();
+    }
+
+    private boolean isNtnConnected() {
+        return mSessionStartTimeSec != 0;
+    }
+
+    private void updateNtnRoamingInHomeCountry(Phone phone) {
+        int subId = phone.getSubId();
+        ServiceState serviceState = phone.getServiceState();
+        if (serviceState == null) {
+            logd("ServiceState is null");
+            return;
+        }
+
+        String satelliteRegisteredPlmn = "";
+        for (NetworkRegistrationInfo nri
+                : serviceState.getNetworkRegistrationInfoList()) {
+            if (nri.isNonTerrestrialNetwork()) {
+                satelliteRegisteredPlmn = nri.getRegisteredPlmn();
+            }
+        }
+
+        SubscriptionInfoInternal subscriptionInfoInternal =
+                mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
+        if (subscriptionInfoInternal == null) {
+            logd("SubscriptionInfoInternal is null");
+            return;
+        }
+        String simCountry = MccTable.countryCodeForMcc(subscriptionInfoInternal.getMcc());
+        String satelliteRegisteredCountry = MccTable.countryCodeForMcc(
+                satelliteRegisteredPlmn.substring(0, 3));
+        if (simCountry.equalsIgnoreCase(satelliteRegisteredCountry)) {
+            mIsNtnRoamingInHomeCountry = false;
+        } else {
+            // If device is connected to roaming non-terrestrial network, update to true.
+            mIsNtnRoamingInHomeCountry = true;
+        }
+        logd("updateNtnRoamingInHomeCountry: mIsNtnRoamingInHomeCountry="
+                + mIsNtnRoamingInHomeCountry);
+    }
+
+    private void logd(@NonNull String log) {
+        Log.d(TAG, log);
+    }
+
+    private void loge(@NonNull String log) {
+        Log.e(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/ConfigUpdaterMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/ConfigUpdaterMetricsStats.java
new file mode 100644
index 0000000..c379b83
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/metrics/ConfigUpdaterMetricsStats.java
@@ -0,0 +1,124 @@
+/*
+ * 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.satellite.metrics;
+
+import android.annotation.NonNull;
+import android.util.Log;
+
+import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.satellite.SatelliteConstants;
+
+public class ConfigUpdaterMetricsStats {
+    private static final String TAG = ConfigUpdaterMetricsStats.class.getSimpleName();
+    private static ConfigUpdaterMetricsStats sInstance = null;
+
+    private int mConfigVersion;
+    private int mOemConfigResult;
+    private int mCarrierConfigResult;
+
+    private ConfigUpdaterMetricsStats() {
+        initializeConfigUpdaterParams();
+    }
+
+    /**
+     * Returns the Singleton instance of ConfigUpdaterMetricsStats class.
+     * If an instance of the Singleton class has not been created,
+     * it creates a new instance and returns it. Otherwise, it returns
+     * the existing instance.
+     * @return the Singleton instance of ConfigUpdaterMetricsStats
+     */
+    public static ConfigUpdaterMetricsStats getOrCreateInstance() {
+        if (sInstance == null) {
+            logd("Create new ConfigUpdaterMetricsStats.");
+            sInstance = new ConfigUpdaterMetricsStats();
+        }
+        return sInstance;
+    }
+
+    /** Set config version for config updater metrics */
+    public ConfigUpdaterMetricsStats setConfigVersion(int configVersion) {
+        mConfigVersion = configVersion;
+        return this;
+    }
+
+    /** Set oem config result for config updater metrics */
+    public ConfigUpdaterMetricsStats setOemConfigResult(int oemConfigResult) {
+        mOemConfigResult = oemConfigResult;
+        return this;
+    }
+
+    /** Set carrier config result for config updater metrics */
+    public ConfigUpdaterMetricsStats setCarrierConfigResult(int carrierConfigResult) {
+        mCarrierConfigResult = carrierConfigResult;
+        return this;
+    }
+
+    /** Report metrics on oem config update error */
+    public void reportOemConfigError(int error) {
+        mOemConfigResult = error;
+        reportConfigUpdaterMetrics();
+    }
+
+    /** Report metrics on carrier config update error */
+    public void reportCarrierConfigError(int error) {
+        mCarrierConfigResult = error;
+        reportConfigUpdaterMetrics();
+    }
+
+    /** Report metrics on config update error */
+    public void reportOemAndCarrierConfigError(int error) {
+        mOemConfigResult = error;
+        mCarrierConfigResult = error;
+        reportConfigUpdaterMetrics();
+    }
+
+    /** Report metrics on config update success */
+    public void reportConfigUpdateSuccess() {
+        mOemConfigResult = SatelliteConstants.CONFIG_UPDATE_RESULT_SUCCESS;
+        mCarrierConfigResult = SatelliteConstants.CONFIG_UPDATE_RESULT_SUCCESS;
+        reportConfigUpdaterMetrics();
+    }
+
+
+    /** Report config updater metrics atom to PersistAtomsStorage in telephony */
+    private void reportConfigUpdaterMetrics() {
+        SatelliteStats.SatelliteConfigUpdaterParams configUpdaterParams =
+                new SatelliteStats.SatelliteConfigUpdaterParams.Builder()
+                        .setConfigVersion(mConfigVersion)
+                        .setOemConfigResult(mOemConfigResult)
+                        .setCarrierConfigResult(mCarrierConfigResult)
+                        .setCount(1)
+                        .build();
+        SatelliteStats.getInstance().onSatelliteConfigUpdaterMetrics(configUpdaterParams);
+        logd("reportConfigUpdaterMetrics: " + configUpdaterParams);
+
+        CarrierRoamingSatelliteControllerStats.getOrCreateInstance()
+                .reportCountOfSatelliteConfigUpdateRequest();
+
+        initializeConfigUpdaterParams();
+    }
+
+    private void initializeConfigUpdaterParams() {
+        mConfigVersion = -1;
+        mOemConfigResult = SatelliteConstants.CONFIG_UPDATE_RESULT_UNKNOWN;
+        mCarrierConfigResult = SatelliteConstants.CONFIG_UPDATE_RESULT_UNKNOWN;
+    }
+
+    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 0c18fac..7dff2e6 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/ControllerMetricsStats.java
@@ -27,6 +27,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.satellite.SatelliteServiceUtils;
 
 /**
  * Stats to log to satellite metrics
@@ -132,63 +133,73 @@
 
     /** Report a counter when an attempt for outgoing datagram is successfully done */
     public void reportOutgoingDatagramSuccessCount(
-            @NonNull @SatelliteManager.DatagramType int datagramType) {
-        SatelliteStats.SatelliteControllerParams controllerParam;
-        if (datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
-            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
-                    .setCountOfOutgoingDatagramSuccess(ADD_COUNT)
-                    .setCountOfDatagramTypeSosSmsSuccess(ADD_COUNT)
-                    .build();
-        } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) {
-            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
-                    .setCountOfOutgoingDatagramSuccess(ADD_COUNT)
-                    .setCountOfDatagramTypeLocationSharingSuccess(ADD_COUNT)
-                    .build();
-        } else { // datagramType == SatelliteManager.DATAGRAM_TYPE_UNKNOWN
-            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
-                    .setCountOfOutgoingDatagramSuccess(ADD_COUNT)
-                    .build();
+            @NonNull @SatelliteManager.DatagramType int datagramType, boolean isDemoMode) {
+        SatelliteStats.SatelliteControllerParams.Builder builder =
+                new SatelliteStats.SatelliteControllerParams.Builder();
+
+        if (isDemoMode) {
+            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();
+        }
+
+        SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
         logd("reportServiceEnablementSuccessCount(): " + controllerParam);
         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
     }
 
     /** Report a counter when an attempt for outgoing datagram is failed */
     public void reportOutgoingDatagramFailCount(
-            @NonNull @SatelliteManager.DatagramType int datagramType) {
-        SatelliteStats.SatelliteControllerParams controllerParam;
-        if (datagramType == SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE) {
-            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
-                    .setCountOfOutgoingDatagramFail(ADD_COUNT)
-                    .setCountOfDatagramTypeSosSmsFail(ADD_COUNT)
-                    .build();
-        } else if (datagramType == SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING) {
-            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
-                    .setCountOfOutgoingDatagramFail(ADD_COUNT)
-                    .setCountOfDatagramTypeLocationSharingFail(ADD_COUNT)
-                    .build();
-        } else { // datagramType == SatelliteManager.DATAGRAM_TYPE_UNKNOWN
-            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
-                    .setCountOfOutgoingDatagramFail(ADD_COUNT)
-                    .build();
+            @NonNull @SatelliteManager.DatagramType int datagramType, boolean isDemoMode) {
+        SatelliteStats.SatelliteControllerParams.Builder builder =
+                new SatelliteStats.SatelliteControllerParams.Builder();
+
+        if (isDemoMode) {
+            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);
+        }
+
+        SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
         logd("reportOutgoingDatagramFailCount(): " + controllerParam);
         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
     }
 
     /** Report a counter when an attempt for incoming datagram is failed */
     public void reportIncomingDatagramCount(
-            @NonNull @SatelliteManager.SatelliteResult int result) {
-        SatelliteStats.SatelliteControllerParams controllerParam;
-        if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
-            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
-                    .setCountOfIncomingDatagramSuccess(ADD_COUNT)
-                    .build();
+            @NonNull @SatelliteManager.SatelliteResult int result, boolean isDemoMode) {
+        SatelliteStats.SatelliteControllerParams.Builder builder =
+                new SatelliteStats.SatelliteControllerParams.Builder();
+        if (isDemoMode) {
+            if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
+                builder.setCountOfDemoModeIncomingDatagramSuccess(ADD_COUNT);
+            } else {
+                builder.setCountOfDemoModeIncomingDatagramFail(ADD_COUNT);
+            }
         } else {
-            controllerParam = new SatelliteStats.SatelliteControllerParams.Builder()
-                    .setCountOfIncomingDatagramFail(ADD_COUNT)
-                    .build();
+            if (result == SatelliteManager.SATELLITE_RESULT_SUCCESS) {
+                builder.setCountOfIncomingDatagramSuccess(ADD_COUNT);
+            } else {
+                builder.setCountOfIncomingDatagramFail(ADD_COUNT);
+            }
         }
+        SatelliteStats.SatelliteControllerParams  controllerParam = builder.build();
         logd("reportIncomingDatagramCount(): " + controllerParam);
         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
     }
@@ -225,6 +236,36 @@
         mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
     }
 
+    /**
+     * Report a counter when checking result whether satellite communication is allowed or not for
+     * current location.
+     */
+    public void reportAllowedSatelliteAccessCount(boolean isAllowed) {
+        SatelliteStats.SatelliteControllerParams.Builder builder;
+        if (isAllowed) {
+            builder = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfAllowedSatelliteAccess(ADD_COUNT);
+        } else {
+            builder = new SatelliteStats.SatelliteControllerParams.Builder()
+                    .setCountOfDisallowedSatelliteAccess(ADD_COUNT);
+        }
+        SatelliteStats.SatelliteControllerParams controllerParam = builder.build();
+        logd("reportAllowedSatelliteAccessCount:" + controllerParam);
+        mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
+    }
+
+    /**
+     * Report a counter when checking whether satellite communication for current location is
+     * allowed has failed.
+     */
+    public void reportFailedSatelliteAccessCheckCount() {
+        SatelliteStats.SatelliteControllerParams controllerParam =
+                new SatelliteStats.SatelliteControllerParams.Builder()
+                        .setCountOfSatelliteAccessCheckFail(ADD_COUNT).build();
+        logd("reportFailedSatelliteAccessCheckCount:" + controllerParam);
+        mSatelliteStats.onSatelliteControllerMetrics(controllerParam);
+    }
+
     /** Return the total service up time for satellite service */
     @VisibleForTesting
     public int captureTotalServiceUpTimeSec() {
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/EntitlementMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/EntitlementMetricsStats.java
new file mode 100644
index 0000000..4862188
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/metrics/EntitlementMetricsStats.java
@@ -0,0 +1,103 @@
+/*
+ * 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.satellite.metrics;
+
+import android.annotation.NonNull;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.satellite.SatelliteConstants;
+
+public class EntitlementMetricsStats {
+    private static final String TAG = EntitlementMetricsStats.class.getSimpleName();
+    private static EntitlementMetricsStats sInstance = null;
+    private static final int RESULT_SUCCESS = 200;
+
+    private int mSubId;
+    private int mResult;
+    private int mEntitlementStatus;
+    private boolean mIsRetry;
+
+    private EntitlementMetricsStats() {}
+
+    /**
+     * Returns the Singleton instance of EntitlementMetricsStats class.
+     * If an instance of the Singleton class has not been created,
+     * it creates a new instance and returns it. Otherwise, it returns
+     * the existing instance.
+     * @return the Singleton instance of EntitlementMetricsStats
+     */
+    public static EntitlementMetricsStats getOrCreateInstance() {
+        if (sInstance == null) {
+            logd("Create new EntitlementMetricsStats.");
+            sInstance = new EntitlementMetricsStats();
+        }
+        return sInstance;
+    }
+
+    /** Report metrics on entitlement query request success */
+    public void reportSuccess(int subId,
+            @SatelliteConstants.SatelliteEntitlementStatus int entitlementStatus,
+            boolean isRetry) {
+        mSubId = subId;
+        mResult = RESULT_SUCCESS;
+        mEntitlementStatus = entitlementStatus;
+        mIsRetry = isRetry;
+        reportEntitlementMetrics();
+    }
+
+    /** Report metrics on entitlement query request error */
+    public void reportError(int subId, int result, boolean isRetry) {
+        mSubId = subId;
+        mResult = result;
+        mIsRetry = isRetry;
+        mEntitlementStatus = SatelliteConstants.SATELLITE_ENTITLEMENT_STATUS_UNKNOWN;
+        reportEntitlementMetrics();
+    }
+
+    /** Report entitlement metrics atom to PersistAtomsStorage in telephony */
+    private void reportEntitlementMetrics() {
+        SatelliteStats.SatelliteEntitlementParams entitlementParams =
+                new SatelliteStats.SatelliteEntitlementParams.Builder()
+                        .setCarrierId(getCarrierId(mSubId))
+                        .setResult(mResult)
+                        .setEntitlementStatus(mEntitlementStatus)
+                        .setIsRetry(mIsRetry)
+                        .setCount(1)
+                        .build();
+        SatelliteStats.getInstance().onSatelliteEntitlementMetrics(entitlementParams);
+        logd("reportEntitlementMetrics: " + entitlementParams);
+
+        CarrierRoamingSatelliteControllerStats.getOrCreateInstance()
+                .reportCountOfEntitlementStatusQueryRequest();
+    }
+
+    /** Returns the carrier ID of the given subscription id. */
+    private int getCarrierId(int subId) {
+        int phoneId = SubscriptionManager.getPhoneId(subId);
+        Phone phone = PhoneFactory.getPhone(phoneId);
+        return phone != null ? phone.getCarrierId() : TelephonyManager.UNKNOWN_CARRIER_ID;
+    }
+
+    private static void logd(@NonNull String log) {
+        Log.d(TAG, log);
+    }
+}
diff --git a/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java b/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java
index d48c488..0647231 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/ProvisionMetricsStats.java
@@ -91,7 +91,7 @@
                         .setIsCanceled(mIsCanceled)
                         .build();
         SatelliteStats.getInstance().onSatelliteProvisionMetrics(provisionParams);
-        logd(provisionParams.toString());
+        logd("reportProvisionMetrics: " + provisionParams.toString());
         initializeProvisionParams();
     }
 
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 6585bec..65181c0 100644
--- a/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java
+++ b/src/java/com/android/internal/telephony/satellite/metrics/SessionMetricsStats.java
@@ -16,11 +16,19 @@
 
 package com.android.internal.telephony.satellite.metrics;
 
+import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
+
 import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteManager;
+import android.telephony.satellite.SatelliteSessionStats;
 import android.util.Log;
 
 import com.android.internal.telephony.metrics.SatelliteStats;
+import com.android.internal.telephony.satellite.DatagramDispatcher;
 
 /**
  * Stats to log to satellite session metrics
@@ -32,6 +40,18 @@
     private static SessionMetricsStats sInstance = null;
     private @SatelliteManager.SatelliteResult int mInitializationResult;
     private @SatelliteManager.NTRadioTechnology int mRadioTechnology;
+    private @SatelliteManager.SatelliteResult int mTerminationResult;
+    private long mInitializationProcessingTimeMillis;
+    private long mTerminationProcessingTimeMillis;
+    private int mSessionDurationSec;
+    private int mCountOfSuccessfulOutgoingDatagram;
+    private int mCountOfFailedOutgoingDatagram;
+    private int mCountOfTimedOutUserMessagesWaitingForConnection;
+    private int mCountOfTimedOutUserMessagesWaitingForAck;
+    private int mCountOfSuccessfulIncomingDatagram;
+    private int mCountOfIncomingDatagramFailed;
+    private boolean mIsDemoMode;
+    private @NtnSignalStrength.NtnSignalStrengthLevel int mMaxNtnSignalStrengthLevel;
 
     private SessionMetricsStats() {
         initializeSessionMetricsParam();
@@ -42,7 +62,7 @@
      * If an instance of the Singleton class has not been created,
      * it creates a new instance and returns it. Otherwise, it returns
      * the existing instance.
-     * @return the Singleton instance of SessionMetricsStats
+     * @return the Singleton instance of SessionMetricsStats.
      */
     public static SessionMetricsStats getInstance() {
         if (sInstance == null) {
@@ -52,7 +72,7 @@
         return sInstance;
     }
 
-    /** Sets the satellite initialization result */
+    /** Sets the satellite initialization result. */
     public SessionMetricsStats setInitializationResult(
             @SatelliteManager.SatelliteResult int result) {
         logd("setInitializationResult(" + result + ")");
@@ -60,29 +80,203 @@
         return this;
     }
 
-    /** Sets the satellite ratio technology */
-    public SessionMetricsStats setRadioTechnology(
+    /** Sets the satellite ratio technology. */
+    public SessionMetricsStats setSatelliteTechnology(
             @SatelliteManager.NTRadioTechnology int radioTechnology) {
-        logd("setRadioTechnology(" + radioTechnology + ")");
+        logd("setSatelliteTechnology(" + radioTechnology + ")");
         mRadioTechnology = radioTechnology;
         return this;
     }
 
-    /** Report the session metrics atoms to PersistAtomsStorage in telephony */
+    /** Sets the satellite de-initialization result. */
+    public SessionMetricsStats setTerminationResult(
+            @SatelliteManager.SatelliteResult int result) {
+        logd("setTerminationResult(" + result + ")");
+        mTerminationResult = result;
+        return this;
+    }
+
+    /** Sets the satellite initialization processing time. */
+    public SessionMetricsStats setInitializationProcessingTime(long processingTime) {
+        logd("setInitializationProcessingTime(" + processingTime + ")");
+        mInitializationProcessingTimeMillis = processingTime;
+        return this;
+    }
+
+    /** Sets the satellite de-initialization processing time. */
+    public SessionMetricsStats setTerminationProcessingTime(long processingTime) {
+        logd("setTerminationProcessingTime(" + processingTime + ")");
+        mTerminationProcessingTimeMillis = processingTime;
+        return this;
+    }
+
+    /** Sets the total enabled time for the satellite session. */
+    public SessionMetricsStats setSessionDurationSec(int sessionDurationSec) {
+        logd("setSessionDuration(" + sessionDurationSec + ")");
+        mSessionDurationSec = sessionDurationSec;
+        return this;
+    }
+
+    /** Increase the count of successful outgoing datagram transmission. */
+    public SessionMetricsStats addCountOfSuccessfulOutgoingDatagram(
+            @NonNull @SatelliteManager.DatagramType int datagramType) {
+        if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
+            // Ignore KEEP_ALIVE messages
+            return this;
+        }
+
+        mCountOfSuccessfulOutgoingDatagram++;
+        logd("addCountOfSuccessfulOutgoingDatagram: current count="
+                + mCountOfSuccessfulOutgoingDatagram);
+        return this;
+    }
+
+    /** Increase the count of failed outgoing datagram transmission. */
+    public SessionMetricsStats addCountOfFailedOutgoingDatagram(
+            @NonNull @SatelliteManager.DatagramType int datagramType,
+            @NonNull @SatelliteManager.SatelliteResult int resultCode) {
+        if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
+            // Ignore KEEP_ALIVE messages
+            return this;
+        }
+
+        mCountOfFailedOutgoingDatagram++;
+        logd("addCountOfFailedOutgoingDatagram: current count=" + mCountOfFailedOutgoingDatagram);
+
+        if (resultCode == SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE) {
+            addCountOfTimedOutUserMessagesWaitingForConnection(datagramType);
+        } else if (resultCode == SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT) {
+            addCountOfTimedOutUserMessagesWaitingForAck(datagramType);
+        }
+
+        return this;
+    }
+
+    /** Increase the count of user messages that timed out waiting for connection. */
+    private SessionMetricsStats addCountOfTimedOutUserMessagesWaitingForConnection(
+            @NonNull @SatelliteManager.DatagramType int datagramType) {
+        if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
+            // Ignore KEEP_ALIVE messages
+            return this;
+        }
+
+        mCountOfTimedOutUserMessagesWaitingForConnection++;
+        logd("addCountOfTimedOutUserMessagesWaitingForConnection: current count="
+                + mCountOfTimedOutUserMessagesWaitingForConnection);
+        return this;
+    }
+
+    /** Increase the count of user messages that timed out waiting for ack. */
+    private SessionMetricsStats addCountOfTimedOutUserMessagesWaitingForAck(
+            @NonNull @SatelliteManager.DatagramType int datagramType) {
+        if (datagramType == SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE) {
+            // Ignore KEEP_ALIVE messages
+            return this;
+        }
+
+        mCountOfTimedOutUserMessagesWaitingForAck++;
+        logd("addCountOfTimedOutUserMessagesWaitingForAck: current count="
+                + mCountOfTimedOutUserMessagesWaitingForAck);
+        return this;
+    }
+
+    /** Increase the count of successful incoming datagram transmission. */
+    public SessionMetricsStats addCountOfSuccessfulIncomingDatagram() {
+        mCountOfSuccessfulIncomingDatagram++;
+        logd("addCountOfSuccessfulIncomingDatagram: current count="
+                + mCountOfSuccessfulIncomingDatagram);
+        return this;
+    }
+
+    /** Increase the count of failed incoming datagram transmission. */
+    public SessionMetricsStats addCountOfFailedIncomingDatagram() {
+        mCountOfIncomingDatagramFailed++;
+        logd("addCountOfFailedIncomingDatagram: current count=" + mCountOfIncomingDatagramFailed);
+        return this;
+    }
+
+    /** Sets whether the session is enabled for demo mode or not. */
+    public SessionMetricsStats setIsDemoMode(boolean isDemoMode) {
+        mIsDemoMode = isDemoMode;
+        logd("setIsDemoMode(" + mIsDemoMode + ")");
+        return this;
+    }
+
+    /** Updates the max Ntn signal strength level for the session. */
+    public SessionMetricsStats updateMaxNtnSignalStrengthLevel(
+            @NtnSignalStrength.NtnSignalStrengthLevel int latestNtnSignalStrengthLevel) {
+        if (latestNtnSignalStrengthLevel > mMaxNtnSignalStrengthLevel) {
+            mMaxNtnSignalStrengthLevel = latestNtnSignalStrengthLevel;
+        }
+        logd("updateMaxNtnSignalsStrength: latest signal strength=" + latestNtnSignalStrengthLevel
+                + ", max signal strength=" + mMaxNtnSignalStrengthLevel);
+        return this;
+    }
+
+    /** Report the session metrics atoms to PersistAtomsStorage in telephony. */
     public void reportSessionMetrics() {
         SatelliteStats.SatelliteSessionParams sessionParams =
                 new SatelliteStats.SatelliteSessionParams.Builder()
                         .setSatelliteServiceInitializationResult(mInitializationResult)
                         .setSatelliteTechnology(mRadioTechnology)
+                        .setTerminationResult(mTerminationResult)
+                        .setInitializationProcessingTime(mInitializationProcessingTimeMillis)
+                        .setTerminationProcessingTime(mTerminationProcessingTimeMillis)
+                        .setSessionDuration(mSessionDurationSec)
+                        .setCountOfOutgoingDatagramSuccess(mCountOfSuccessfulOutgoingDatagram)
+                        .setCountOfOutgoingDatagramFailed(mCountOfFailedOutgoingDatagram)
+                        .setCountOfIncomingDatagramSuccess(mCountOfSuccessfulIncomingDatagram)
+                        .setCountOfIncomingDatagramFailed(mCountOfIncomingDatagramFailed)
+                        .setIsDemoMode(mIsDemoMode)
+                        .setMaxNtnSignalStrengthLevel(mMaxNtnSignalStrengthLevel)
                         .build();
-        logd(sessionParams.toString());
+        logd("reportSessionMetrics: " + sessionParams.toString());
         SatelliteStats.getInstance().onSatelliteSessionMetrics(sessionParams);
         initializeSessionMetricsParam();
     }
 
+    /** Returns {@link SatelliteSessionStats} of the satellite service. */
+    public void requestSatelliteSessionStats(int subId, @NonNull ResultReceiver result) {
+        Bundle bundle = new Bundle();
+        SatelliteSessionStats sessionStats = new SatelliteSessionStats.Builder()
+                .setCountOfSuccessfulUserMessages(mCountOfSuccessfulOutgoingDatagram)
+                .setCountOfUnsuccessfulUserMessages(mCountOfFailedOutgoingDatagram)
+                .setCountOfTimedOutUserMessagesWaitingForConnection(
+                        mCountOfTimedOutUserMessagesWaitingForConnection)
+                .setCountOfTimedOutUserMessagesWaitingForAck(
+                        mCountOfTimedOutUserMessagesWaitingForAck)
+                .setCountOfUserMessagesInQueueToBeSent(
+                        DatagramDispatcher.getInstance().getPendingUserMessagesCount())
+                .build();
+        bundle.putParcelable(SatelliteManager.KEY_SESSION_STATS, sessionStats);
+        result.send(SATELLITE_RESULT_SUCCESS, bundle);
+    }
+
+    /** Returns the processing time for satellite session initialization. */
+    public long getSessionInitializationProcessingTimeMillis() {
+        return mInitializationProcessingTimeMillis;
+    }
+
+    /** Returns the processing time for satellite session termination. */
+    public long getSessionTerminationProcessingTimeMillis() {
+        return mTerminationProcessingTimeMillis;
+    }
+
     private void initializeSessionMetricsParam() {
         mInitializationResult = SatelliteManager.SATELLITE_RESULT_SUCCESS;
         mRadioTechnology = SatelliteManager.NT_RADIO_TECHNOLOGY_UNKNOWN;
+        mTerminationResult = SatelliteManager.SATELLITE_RESULT_SUCCESS;
+        mInitializationProcessingTimeMillis = 0;
+        mTerminationProcessingTimeMillis = 0;
+        mSessionDurationSec = 0;
+        mCountOfSuccessfulOutgoingDatagram = 0;
+        mCountOfFailedOutgoingDatagram = 0;
+        mCountOfTimedOutUserMessagesWaitingForConnection = 0;
+        mCountOfTimedOutUserMessagesWaitingForAck = 0;
+        mCountOfSuccessfulIncomingDatagram = 0;
+        mCountOfIncomingDatagramFailed = 0;
+        mIsDemoMode = false;
+        mMaxNtnSignalStrengthLevel = NTN_SIGNAL_STRENGTH_NONE;
     }
 
     private static void logd(@NonNull String log) {
diff --git a/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java b/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java
index 34f26e3..2d4776c 100644
--- a/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java
+++ b/src/java/com/android/internal/telephony/security/CellularNetworkSecuritySafetySource.java
@@ -25,11 +25,14 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
 import android.safetycenter.SafetyCenterManager;
 import android.safetycenter.SafetyEvent;
 import android.safetycenter.SafetySourceData;
 import android.safetycenter.SafetySourceIssue;
 import android.safetycenter.SafetySourceStatus;
+import android.text.format.DateFormat;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -39,8 +42,10 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.time.Instant;
-import java.util.Date;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.stream.Stream;
@@ -62,10 +67,6 @@
 
     private static final Intent CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT =
             new Intent("android.settings.CELLULAR_NETWORK_SECURITY");
-    // TODO(b/321999913): direct to a help page URL e.g.
-    //                    new Intent(Intent.ACTION_VIEW, Uri.parse("https://..."));
-    private static final Intent LEARN_MORE_INTENT = new Intent();
-
     static final int NULL_CIPHER_STATE_ENCRYPTED = 0;
     static final int NULL_CIPHER_STATE_NOTIFY_ENCRYPTED = 1;
     static final int NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED = 2;
@@ -123,6 +124,13 @@
     }
 
     /**
+     * Clears issue state for the identified subscription
+     */
+    public synchronized  void clearNullCipherState(Context context, int subId) {
+        mNullCipherStates.remove(subId);
+        updateSafetyCenter(context);
+    }
+    /**
      * Enables or disables the identifier disclosure issue and clears any current issues if the
      * enable state is changed.
      */
@@ -209,6 +217,7 @@
         SubscriptionInfoInternal subInfo =
                 mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
         final SafetySourceIssue.Builder builder;
+        final SafetySourceIssue.Notification customNotification;
         switch (state) {
             case NULL_CIPHER_STATE_ENCRYPTED:
                 return Optional.empty();
@@ -216,43 +225,70 @@
                 builder = new SafetySourceIssue.Builder(
                         NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID + "_" + subId,
                         context.getString(
-                            R.string.scNullCipherIssueNonEncryptedTitle, subInfo.getDisplayName()),
-                        context.getString(R.string.scNullCipherIssueNonEncryptedSummary),
+                                R.string.scNullCipherIssueNonEncryptedTitle),
+                        context.getString(
+                              R.string.scNullCipherIssueNonEncryptedSummary,
+                              subInfo.getDisplayName()),
                         SEVERITY_LEVEL_RECOMMENDATION,
                         NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID);
+                customNotification =
+                         new SafetySourceIssue.Notification.Builder(
+                                context.getString(R.string.scNullCipherIssueNonEncryptedTitle),
+                                context.getString(
+                                        R.string.scNullCipherIssueNonEncryptedSummaryNotification,
+                                        subInfo.getDisplayName()))
+                        .build();
                 break;
             case NULL_CIPHER_STATE_NOTIFY_ENCRYPTED:
                 builder = new SafetySourceIssue.Builder(
                         NULL_CIPHER_ISSUE_NON_ENCRYPTED_ID + "_" + subId,
                         context.getString(
-                            R.string.scNullCipherIssueEncryptedTitle, subInfo.getDisplayName()),
-                        context.getString(R.string.scNullCipherIssueEncryptedSummary),
+                                R.string.scNullCipherIssueEncryptedTitle,
+                                subInfo.getDisplayName()),
+                        context.getString(
+                                R.string.scNullCipherIssueEncryptedSummary,
+                                subInfo.getDisplayName()),
                         SEVERITY_LEVEL_INFORMATION,
                         NULL_CIPHER_ISSUE_ENCRYPTED_ID);
+                customNotification =
+                        new SafetySourceIssue.Notification.Builder(
+                                context.getString(
+                                      R.string.scNullCipherIssueEncryptedTitle,
+                                      subInfo.getDisplayName()),
+                                context.getString(
+                                      R.string.scNullCipherIssueEncryptedSummary,
+                                      subInfo.getDisplayName()))
+                        .build();
                 break;
             default:
                 throw new AssertionError();
         }
-
-        return Optional.of(
-                builder
-                    .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY)
-                    .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
-                    .addAction(
+        builder
+                .setNotificationBehavior(
+                        SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY)
+                .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DATA)
+                .setCustomNotification(customNotification)
+                .addAction(
                         new SafetySourceIssue.Action.Builder(
                                 NULL_CIPHER_ACTION_SETTINGS_ID,
                                 context.getString(R.string.scNullCipherIssueActionSettings),
                                 mSafetyCenterManagerWrapper.getActivityPendingIntent(
                                         context, CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT))
-                            .build())
-                    .addAction(
-                        new SafetySourceIssue.Action.Builder(
-                                NULL_CIPHER_ACTION_LEARN_MORE_ID,
-                                context.getString(R.string.scNullCipherIssueActionLearnMore),
-                                mSafetyCenterManagerWrapper.getActivityPendingIntent(
-                                        context, LEARN_MORE_INTENT))
-                            .build())
-                    .build());
+                                .build());
+
+        Intent learnMoreIntent = getLearnMoreIntent(context);
+        if (learnMoreIntent != null) {
+            builder.addAction(
+                    new SafetySourceIssue.Action.Builder(
+                            NULL_CIPHER_ACTION_LEARN_MORE_ID,
+                            context.getString(
+                                    R.string.scNullCipherIssueActionLearnMore),
+                            mSafetyCenterManagerWrapper.getActivityPendingIntent(
+                                    context, learnMoreIntent))
+                            .build());
+        }
+
+        return Optional.of(builder.build());
     }
 
     /** Builds the identity disclosure issue if it's enabled and there are disclosures to report. */
@@ -264,35 +300,77 @@
 
         SubscriptionInfoInternal subInfo =
                 mSubscriptionManagerService.getSubscriptionInfoInternal(subId);
-        return Optional.of(
+
+        // Notifications have no buttons
+        final SafetySourceIssue.Notification customNotification =
+                new SafetySourceIssue.Notification.Builder(
+                        context.getString(R.string.scIdentifierDisclosureIssueTitle),
+                        context.getString(
+                                R.string.scIdentifierDisclosureIssueSummaryNotification,
+                                getCurrentTime(),
+                                subInfo.getDisplayName())).build();
+        SafetySourceIssue.Builder builder =
                 new SafetySourceIssue.Builder(
                         IDENTIFIER_DISCLOSURE_ISSUE_ID + "_" + subId,
                         context.getString(R.string.scIdentifierDisclosureIssueTitle),
                         context.getString(
                                 R.string.scIdentifierDisclosureIssueSummary,
-                                disclosure.getDisclosureCount(),
-                                Date.from(disclosure.getWindowStart()),
-                                Date.from(disclosure.getWindowEnd()),
+                                getCurrentTime(),
                                 subInfo.getDisplayName()),
                         SEVERITY_LEVEL_RECOMMENDATION,
                         IDENTIFIER_DISCLOSURE_ISSUE_ID)
-                    .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY)
-                    .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
-                    .addAction(
-                        new SafetySourceIssue.Action.Builder(
-                                NULL_CIPHER_ACTION_SETTINGS_ID,
-                                context.getString(R.string.scNullCipherIssueActionSettings),
-                                mSafetyCenterManagerWrapper.getActivityPendingIntent(
-                                        context, CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT))
-                            .build())
-                    .addAction(
-                        new SafetySourceIssue.Action.Builder(
-                                NULL_CIPHER_ACTION_LEARN_MORE_ID,
-                                context.getString(R.string.scNullCipherIssueActionLearnMore),
-                                mSafetyCenterManagerWrapper.getActivityPendingIntent(
-                                        context, LEARN_MORE_INTENT))
-                            .build())
-                .build());
+                        .setNotificationBehavior(
+                                SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY)
+                        .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DATA)
+                        .setCustomNotification(customNotification)
+                        .addAction(
+                                new SafetySourceIssue.Action.Builder(
+                                        NULL_CIPHER_ACTION_SETTINGS_ID,
+                                        context.getString(
+                                                R.string.scNullCipherIssueActionSettings),
+                                        mSafetyCenterManagerWrapper.getActivityPendingIntent(
+                                                context,
+                                                CELLULAR_NETWORK_SECURITY_SETTINGS_INTENT))
+                                        .build());
+
+        Intent learnMoreIntent = getLearnMoreIntent(context);
+        if (learnMoreIntent != null) {
+            builder.addAction(
+                    new SafetySourceIssue.Action.Builder(
+                            NULL_CIPHER_ACTION_LEARN_MORE_ID,
+                            context.getString(R.string.scNullCipherIssueActionLearnMore),
+                            mSafetyCenterManagerWrapper.getActivityPendingIntent(
+                                    context, learnMoreIntent)).build()
+            );
+        }
+
+        return Optional.of(builder.build());
+    }
+
+    private String getCurrentTime() {
+        String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "hh:mm");
+        return Instant.now().atZone(ZoneId.systemDefault())
+              .format(DateTimeFormatter.ofPattern(pattern)).toString();
+    }
+
+    /**
+     * Return Intent for learn more action, or null if resource associated with the Intent
+     * uri is
+     * missing or empty.
+     */
+    private Intent getLearnMoreIntent(Context context) {
+        String learnMoreUri;
+        try {
+            learnMoreUri = context.getString(R.string.scCellularNetworkSecurityLearnMore);
+        } catch (Resources.NotFoundException e) {
+            return null;
+        }
+
+        if (learnMoreUri.isEmpty()) {
+            return null;
+        }
+
+        return new Intent(Intent.ACTION_VIEW, Uri.parse(learnMoreUri));
     }
 
     /** A wrapper around {@link SafetyCenterManager} that can be instrumented in tests. */
@@ -337,7 +415,7 @@
         private IdentifierDisclosure(int count, Instant start, Instant end) {
             mDisclosureCount = count;
             mWindowStart = start;
-            mWindowEnd  = end;
+            mWindowEnd = end;
         }
 
         private int getDisclosureCount() {
diff --git a/src/java/com/android/internal/telephony/security/NullCipherNotifier.java b/src/java/com/android/internal/telephony/security/NullCipherNotifier.java
index 3ece701..e13c5b0 100644
--- a/src/java/com/android/internal/telephony/security/NullCipherNotifier.java
+++ b/src/java/com/android/internal/telephony/security/NullCipherNotifier.java
@@ -56,6 +56,7 @@
 
     private final CellularNetworkSecuritySafetySource mSafetySource;
     private final HashMap<Integer, SubscriptionState> mSubscriptionState = new HashMap<>();
+    private final HashMap<Integer, Integer> mActiveSubscriptions = new HashMap<>();
 
     private final Object mEnabledLock = new Object();
     @GuardedBy("mEnabledLock")
@@ -90,31 +91,78 @@
      * Adds a security algorithm update. If appropriate, this will trigger a user notification.
      */
     public void onSecurityAlgorithmUpdate(
-            Context context, int subId, SecurityAlgorithmUpdate update) {
+            Context context, int phoneId, int subId, SecurityAlgorithmUpdate update) {
         Rlog.d(TAG, "Security algorithm update: subId = " + subId + " " + update);
 
         if (shouldIgnoreUpdate(update)) {
             return;
         }
 
+        if (!isEnabled()) {
+            Rlog.i(TAG, "Ignoring onSecurityAlgorithmUpdate. Notifier is disabled.");
+            return;
+        }
+
         try {
             mSerializedWorkQueue.execute(() -> {
-                SubscriptionState subState = mSubscriptionState.get(subId);
-                if (subState == null) {
-                    subState = new SubscriptionState();
-                    mSubscriptionState.put(subId, subState);
-                }
+                try {
+                    maybeUpdateSubscriptionMapping(context, phoneId, subId);
+                    SubscriptionState subState = mSubscriptionState.get(subId);
+                    if (subState == null) {
+                        subState = new SubscriptionState();
+                        mSubscriptionState.put(subId, subState);
+                    }
 
-                @CellularNetworkSecuritySafetySource.NullCipherState int nullCipherState =
-                        subState.update(update);
-                mSafetySource.setNullCipherState(context, subId, nullCipherState);
+                    @CellularNetworkSecuritySafetySource.NullCipherState int nullCipherState =
+                            subState.update(update);
+                    mSafetySource.setNullCipherState(context, subId, nullCipherState);
+                } catch (Throwable t) {
+                    Rlog.e(TAG, "Failed to execute onSecurityAlgorithmUpdate " + t.getMessage());
+                }
             });
         } catch (RejectedExecutionException e) {
-            Rlog.e(TAG, "Failed to schedule onEnableNotifier: " + e.getMessage());
+            Rlog.e(TAG, "Failed to schedule onSecurityAlgorithmUpdate: " + e.getMessage());
         }
     }
 
     /**
+     * Set or update the current phoneId to subId mapping. When a new subId is mapped to a phoneId,
+     * we update the safety source to clear state of the old subId.
+     */
+    public void setSubscriptionMapping(Context context, int phoneId, int subId) {
+
+        if (!isEnabled()) {
+            Rlog.i(TAG, "Ignoring setSubscriptionMapping. Notifier is disabled.");
+        }
+
+        try {
+            mSerializedWorkQueue.execute(() -> {
+                try {
+                    maybeUpdateSubscriptionMapping(context, phoneId, subId);
+                } catch (Throwable t) {
+                    Rlog.e(TAG, "Failed to update subId mapping. phoneId: " + phoneId + " subId: "
+                            + subId + ". " + t.getMessage());
+                }
+            });
+
+        } catch (RejectedExecutionException e) {
+            Rlog.e(TAG, "Failed to schedule setSubscriptionMapping: " + e.getMessage());
+        }
+    }
+
+    private void maybeUpdateSubscriptionMapping(Context context, int phoneId, int subId) {
+        final Integer oldSubId = mActiveSubscriptions.put(phoneId, subId);
+        if (oldSubId == null || oldSubId == subId) {
+            return;
+        }
+
+        // Our subId was updated for this phone, we should clear this subId's state.
+        mSubscriptionState.remove(oldSubId);
+        mSafetySource.clearNullCipherState(context, oldSubId);
+    }
+
+
+    /**
      * Enables null cipher notification; {@code onSecurityAlgorithmUpdate} will start handling
      * security algorithm updates and send notifications to the user when required.
      */
@@ -285,7 +333,7 @@
     /** The state of security algorithms for a network connection. */
     private static final class ConnectionState {
         private static final ConnectionState UNKNOWN =
-                 new ConnectionState(SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_UNKNOWN);
+                new ConnectionState(SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_UNKNOWN);
 
         private final @SecurityAlgorithm int mEncryption;
         private final @SecurityAlgorithm int mIntegrity;
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
index 3d07d47..7596754 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionDatabaseManager.java
@@ -1044,7 +1044,7 @@
                 throw new IllegalArgumentException("updateSubscription: subscription does not "
                         + "exist. subId=" + subId);
             }
-            if (oldSubInfo.equals(newSubInfo)) return;
+            if (oldSubInfo.equalsDbItemsOnly(newSubInfo)) return;
 
             if (updateDatabase(subId, createDeltaContentValues(oldSubInfo, newSubInfo)) > 0) {
                 mAllSubscriptionInfoInternalCache.put(subId, newSubInfo);
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
index 82af4e8..c6dee7c 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionInfoInternal.java
@@ -454,12 +454,13 @@
      */
     private final int mIsOnlyNonTerrestrialNetwork;
 
-    // Below are the fields that do not exist in the SimInfo table.
+    // This field does not exist in the SimInfo table.
     /**
      * The card ID of the SIM card. This maps uniquely to {@link #mCardString}.
      */
     private final int mCardId;
 
+    // This field does not exist in the SimInfo table.
     /**
      * Whether group of the subscription is disabled. This is only useful if it's a grouped
      * opportunistic subscription. In this case, if all primary (non-opportunistic) subscriptions
@@ -1217,7 +1218,22 @@
      * @return {@code true} if the subscription is visible to the user.
      */
     public boolean isVisible() {
-        return !isOpportunistic() || TextUtils.isEmpty(mGroupUuid);
+        // Provisioning profile
+        if (getProfileClass() == SubscriptionManager.PROFILE_CLASS_PROVISIONING) {
+            return false;
+        }
+
+        // Satellite profile
+        if (getOnlyNonTerrestrialNetwork() == 1) {
+            return false;
+        }
+
+        // Opportunistic profile
+        if (isOpportunistic() && !TextUtils.isEmpty(mGroupUuid)) {
+            return false;
+        }
+
+        return true;
     }
 
     /**
@@ -1355,11 +1371,14 @@
                 + "]";
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        SubscriptionInfoInternal that = (SubscriptionInfoInternal) o;
+    /**
+     * Campare only the columns existing in the SimInfo table and the mapped variables to see if
+     * they are equal.
+     *
+     * @param that SubscriptionInfoInternal to be compared
+     * @return {@code true} if equals.
+     */
+    public boolean equalsDbItemsOnly(@NonNull SubscriptionInfoInternal that) {
         return mId == that.mId && mSimSlotIndex == that.mSimSlotIndex
                 && mDisplayNameSource == that.mDisplayNameSource && mIconTint == that.mIconTint
                 && mDataRoaming == that.mDataRoaming && mIsEmbedded == that.mIsEmbedded
@@ -1392,7 +1411,6 @@
                 && mPortIndex == that.mPortIndex && mUsageSetting == that.mUsageSetting
                 && mLastUsedTPMessageReference == that.mLastUsedTPMessageReference
                 && mUserId == that.mUserId && mIsSatelliteEnabled == that.mIsSatelliteEnabled
-                && mCardId == that.mCardId && mIsGroupDisabled == that.mIsGroupDisabled
                 && mIccId.equals(that.mIccId) && mDisplayName.equals(that.mDisplayName)
                 && mCarrierName.equals(that.mCarrierName) && mNumber.equals(that.mNumber)
                 && mMcc.equals(that.mMcc) && mMnc.equals(that.mMnc) && mEhplmns.equals(
@@ -1416,6 +1434,15 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SubscriptionInfoInternal that = (SubscriptionInfoInternal) o;
+        return equalsDbItemsOnly(that)
+                && mCardId == that.mCardId && mIsGroupDisabled == that.mIsGroupDisabled;
+    }
+
+    @Override
     public int hashCode() {
         int result = Objects.hash(mId, mIccId, mSimSlotIndex, mDisplayName, mCarrierName,
                 mDisplayNameSource, mIconTint, mNumber, mDataRoaming, mMcc, mMnc, mEhplmns, mHplmns,
diff --git a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
index 5e066e1..0e98a9e 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
@@ -42,6 +42,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.CountDownTimer;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -104,6 +105,7 @@
 import com.android.internal.telephony.euicc.EuiccController;
 import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.flags.Flags;
+import com.android.internal.telephony.satellite.SatelliteController;
 import com.android.internal.telephony.subscription.SubscriptionDatabaseManager.SubscriptionDatabaseManagerCallback;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.uicc.IccUtils;
@@ -124,6 +126,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Random;
 import java.util.Set;
 import java.util.UUID;
@@ -142,6 +145,9 @@
     private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
     private static final String BOOT_ALLOW_MOCK_MODEM_PROPERTY = "ro.boot.radio.allow_mock_modem";
 
+    private static final int CHECK_BOOTSTRAP_TIMER_IN_MS = 20 * 60 * 1000; // 20 minutes
+    private static CountDownTimer bootstrapProvisioningTimer;
+
     /** Whether enabling verbose debugging message or not. */
     private static final boolean VDBG = false;
 
@@ -937,9 +943,13 @@
      *
      * @param subId Subscription id.
      * @param groupOwner The group owner to assign to the subscription
+     *
+     * @throws SecurityException if the caller does not have required permissions.
      */
+    @Override
+    @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setGroupOwner(int subId, @NonNull String groupOwner) {
-        // This can throw IllegalArgumentException if the subscription does not exist.
+        enforcePermissions("setGroupOwner", Manifest.permission.MODIFY_PHONE_STATE);
         try {
             mSubscriptionDatabaseManager.setGroupOwner(
                     subId,
@@ -1171,7 +1181,7 @@
                                 SubscriptionManager.INVALID_SIM_SLOT_INDEX,
                                 null, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
                         mSubscriptionDatabaseManager.setDisplayName(subId, mContext.getResources()
-                                .getString(R.string.default_card_name, subId));
+                                .getString(R.string.default_card_name, getCardNumber(subId)));
                         subInfo = mSubscriptionDatabaseManager.getSubscriptionInfoInternal(subId);
                     }
 
@@ -1414,27 +1424,39 @@
         }
 
         if (simState == TelephonyManager.SIM_STATE_ABSENT) {
-            // Re-enable the pSIM when it's removed, so it will be in enabled state when it gets
-            // re-inserted again. (pre-U behavior)
-            List<String> iccIds = getIccIdsOfInsertedPhysicalSims();
-            mSubscriptionDatabaseManager.getAllSubscriptions().stream()
-                    // All the removed pSIMs (Note this could include some erased eSIM that has
-                    // embedded bit removed).
-                    .filter(subInfo -> !iccIds.contains(subInfo.getIccId())
-                            && !subInfo.isEmbedded())
-                    .forEach(subInfo -> {
-                        int subId = subInfo.getSubscriptionId();
-                        log("updateSubscription: Re-enable Uicc application on sub " + subId);
-                        mSubscriptionDatabaseManager.setUiccApplicationsEnabled(subId, true);
-                        // When sim is absent, set the port index to invalid port index.
-                        // (pre-U behavior)
-                        mSubscriptionDatabaseManager.setPortIndex(subId,
-                                TelephonyManager.INVALID_PORT_INDEX);
-                    });
+            SatelliteController satelliteController = SatelliteController.getInstance();
+            boolean isSatelliteEnabledOrBeingEnabled = false;
+            if (satelliteController != null) {
+                isSatelliteEnabledOrBeingEnabled = satelliteController.isSatelliteEnabled()
+                        || satelliteController.isSatelliteBeingEnabled();
+            }
+
+            if (!isSatelliteEnabledOrBeingEnabled) {
+                // Re-enable the pSIM when it's removed, so it will be in enabled state when it gets
+                // re-inserted again. (pre-U behavior)
+                List<String> iccIds = getIccIdsOfInsertedPhysicalSims();
+                mSubscriptionDatabaseManager.getAllSubscriptions().stream()
+                        .filter(subInfo -> !iccIds.contains(subInfo.getIccId())
+                                && !subInfo.isEmbedded())
+                        .forEach(subInfo -> {
+                            int subId = subInfo.getSubscriptionId();
+                            log("updateSubscription: Re-enable Uicc application on sub " + subId);
+                            mSubscriptionDatabaseManager.setUiccApplicationsEnabled(subId, true);
+                            // When sim is absent, set the port index to invalid port index.
+                            // (pre-U behavior)
+                            mSubscriptionDatabaseManager.setPortIndex(subId,
+                                    TelephonyManager.INVALID_PORT_INDEX);
+                        });
+            }
 
             if (mSlotIndexToSubId.containsKey(phoneId)) {
                 markSubscriptionsInactive(phoneId);
             }
+
+            if (Flags.clearCachedImsPhoneNumberWhenDeviceLostImsRegistration()) {
+                // Clear the cached Ims phone number
+                setNumberFromIms(getSubId(phoneId), new String(""));
+            }
         } else if (simState == TelephonyManager.SIM_STATE_NOT_READY) {
             // Check if this is the final state. Only update the subscription if NOT_READY is a
             // final state.
@@ -1447,6 +1469,11 @@
             } else {
                 logl("updateSubscription: UICC app disabled on slot " + phoneId);
                 markSubscriptionsInactive(phoneId);
+
+                if (Flags.clearCachedImsPhoneNumberWhenDeviceLostImsRegistration()) {
+                    // Clear the cached Ims phone number
+                    setNumberFromIms(getSubId(phoneId), new String(""));
+                }
             }
         } else {
             String iccId = getIccId(phoneId);
@@ -1478,7 +1505,8 @@
                     subId = insertSubscriptionInfo(iccId, phoneId, null,
                             SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
                     mSubscriptionDatabaseManager.setDisplayName(subId,
-                            mContext.getResources().getString(R.string.default_card_name, subId));
+                            mContext.getResources().getString(R.string.default_card_name,
+                                    getCardNumber(subId)));
                 } else {
                     subId = subInfo.getSubscriptionId();
                     log("updateSubscription: Found existing subscription. subId= " + subId
@@ -1554,12 +1582,6 @@
                         loge("updateSubscription: ICC card is not available.");
                     }
 
-                    if (Flags.clearCachedImsPhoneNumberWhenDeviceLostImsRegistration()) {
-                        // Clear the cached Ims phone number
-                        // before proceeding with Ims Registration
-                        setNumberFromIms(subId, new String(""));
-                    }
-
                     // Attempt to restore SIM specific settings when SIM is loaded.
                     Bundle result = mContext.getContentResolver().call(
                             SubscriptionManager.SIM_INFO_BACKUP_AND_RESTORE_CONTENT_URI,
@@ -1569,6 +1591,10 @@
                             SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_DATABASE_UPDATED)) {
                         logl("Sim specific settings changed the database.");
                         mSubscriptionDatabaseManager.reloadDatabaseSync();
+                        if (mFeatureFlags.backupAndRestoreForEnable2g()) {
+                            PhoneFactory.getPhone(phoneId)
+                                    .loadAllowedNetworksFromSubscriptionDatabase();
+                        }
                     }
                 }
 
@@ -1588,6 +1614,43 @@
 
         updateGroupDisabled();
         updateDefaultSubId();
+
+        if (mSlotIndexToSubId.containsKey(phoneId) &&
+                isEsimBootStrapProvisioningActiveForSubId(mSlotIndexToSubId.get(phoneId))) {
+            startEsimBootstrapTimer();
+        } else {
+            cancelEsimBootstrapTimer();
+        }
+    }
+
+    private void cancelEsimBootstrapTimer() {
+        if (bootstrapProvisioningTimer != null) {
+            bootstrapProvisioningTimer.cancel();
+            bootstrapProvisioningTimer = null;
+            log("bootstrapProvisioningTimer timer cancelled.");
+        }
+    }
+
+    private void startEsimBootstrapTimer() {
+        if (bootstrapProvisioningTimer == null) {
+            bootstrapProvisioningTimer = new CountDownTimer(CHECK_BOOTSTRAP_TIMER_IN_MS,
+                    CHECK_BOOTSTRAP_TIMER_IN_MS) {
+                @Override
+                public void onTick(long millisUntilFinished) {
+                    // Do nothing
+                }
+
+                @Override
+                public void onFinish() {
+                    AnomalyReporter.reportAnomaly(UUID.fromString("40587b0f-27c9-4b39-b94d"
+                                    + "-71fc9771f354"), "eSim bootstrap has been active for too "
+                            + "long.");
+                    log("bootstrapProvisioningTimer: timer finished esim was not disabled.");
+                    cancelEsimBootstrapTimer();
+                }
+            }.start();
+            log("bootstrapProvisioningTimer timer started.");
+        }
     }
 
     /**
@@ -3716,42 +3779,114 @@
             "carrier privileges",
     })
     public String getPhoneNumber(int subId, @PhoneNumberSource int source,
-            @NonNull String callingPackage, @Nullable String callingFeatureId) {
+            @NonNull String callingPackage, @Nullable String callingFeatureId /* unused */) {
         TelephonyPermissions.enforceAnyPermissionGrantedOrCarrierPrivileges(
                 mContext, subId, Binder.getCallingUid(), "getPhoneNumber",
                 Manifest.permission.READ_PHONE_NUMBERS,
                 Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
-
         enforceTelephonyFeatureWithException(callingPackage, "getPhoneNumber");
 
-        final long identity = Binder.clearCallingIdentity();
+        if (mFeatureFlags.saferGetPhoneNumber()) {
+            checkPhoneNumberSource(source);
+            subId = checkAndGetSubId(subId);
+            if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) return "";
 
-        SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                return getPhoneNumberFromSourceInternal(subId, source);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        } else {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
+                        .getSubscriptionInfoInternal(subId);
+
+                if (subInfo == null) {
+                    loge("Invalid sub id " + subId + ", callingPackage=" + callingPackage);
+                    return "";
+                }
+
+                switch(source) {
+                    case SubscriptionManager.PHONE_NUMBER_SOURCE_UICC:
+                        Phone phone = PhoneFactory.getPhone(getSlotIndex(subId));
+                        if (phone != null) {
+                        return TextUtils.emptyIfNull(phone.getLine1Number());
+                        } else {
+                        return subInfo.getNumber();
+                        }
+                    case SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER:
+                        return subInfo.getNumberFromCarrier();
+                    case SubscriptionManager.PHONE_NUMBER_SOURCE_IMS:
+                        return subInfo.getNumberFromIms();
+                    default:
+                        throw new IllegalArgumentException("Invalid number source " + source);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    /**
+     * Get a resolved subId based on what the user passed in.
+     *
+     * Only use this before clearing the calling binder. Used for compatibility (only).
+     * Do not use this behavior for new methods.
+     *
+     * @param subId the subId passed in by the user.
+     */
+    private int checkAndGetSubId(int subId) {
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            // for historical reasons, INVALID_SUB_ID fails gracefully
+            return subId;
+        } else if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
+            return getDefaultSubId();
+        } else if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            throw new IllegalArgumentException("Invalid SubId=" + subId);
+        } else {
+            return subId;
+        }
+    }
+
+    private void checkPhoneNumberSource(int source) {
+        if (source == SubscriptionManager.PHONE_NUMBER_SOURCE_UICC
+                || source == SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER
+                || source == SubscriptionManager.PHONE_NUMBER_SOURCE_IMS) {
+            return;
+        }
+
+        throw new IllegalArgumentException("Invalid number source " + source);
+    }
+
+    private @NonNull String getPhoneNumberFromSourceInternal(
+            int subId,
+            @PhoneNumberSource int source) {
+
+        final SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager
                 .getSubscriptionInfoInternal(subId);
 
         if (subInfo == null) {
-            loge("Invalid sub id " + subId + ", callingPackage=" + callingPackage);
+            loge("No SubscriptionInfo found for subId=" + subId);
             return "";
         }
 
-        try {
-            switch(source) {
-                case SubscriptionManager.PHONE_NUMBER_SOURCE_UICC:
-                    Phone phone = PhoneFactory.getPhone(getSlotIndex(subId));
-                    if (phone != null) {
-                        return TextUtils.emptyIfNull(phone.getLine1Number());
-                    } else {
-                        return subInfo.getNumber();
-                    }
-                case SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER:
-                    return subInfo.getNumberFromCarrier();
-                case SubscriptionManager.PHONE_NUMBER_SOURCE_IMS:
-                    return subInfo.getNumberFromIms();
-                default:
-                    throw new IllegalArgumentException("Invalid number source " + source);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+        switch(source) {
+            case SubscriptionManager.PHONE_NUMBER_SOURCE_UICC:
+                final Phone phone = PhoneFactory.getPhone(getSlotIndex(subId));
+                if (phone != null) {
+                    return TextUtils.emptyIfNull(phone.getLine1Number());
+                } else {
+                    return subInfo.getNumber();
+                }
+            case SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER:
+                return subInfo.getNumberFromCarrier();
+            case SubscriptionManager.PHONE_NUMBER_SOURCE_IMS:
+                return subInfo.getNumberFromIms();
+            default:
+                loge("No SubscriptionInfo found for subId=" + subId);
+                return "";
         }
     }
 
@@ -3787,25 +3922,51 @@
         enforceTelephonyFeatureWithException(callingPackage,
                 "getPhoneNumberFromFirstAvailableSource");
 
-        String numberFromCarrier = getPhoneNumber(subId,
-                SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, callingPackage,
-                callingFeatureId);
-        if (!TextUtils.isEmpty(numberFromCarrier)) {
-            return numberFromCarrier;
+        if (mFeatureFlags.saferGetPhoneNumber()) {
+            subId = checkAndGetSubId(subId);
+            if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) return "";
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                String number;
+                number = getPhoneNumberFromSourceInternal(
+                        subId,
+                        SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER);
+                if (!TextUtils.isEmpty(number)) return number;
+
+                number = getPhoneNumberFromSourceInternal(
+                        subId,
+                        SubscriptionManager.PHONE_NUMBER_SOURCE_UICC);
+                if (!TextUtils.isEmpty(number)) return number;
+
+                number = getPhoneNumberFromSourceInternal(
+                        subId,
+                        SubscriptionManager.PHONE_NUMBER_SOURCE_IMS);
+                return TextUtils.emptyIfNull(number);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        } else {
+            String numberFromCarrier = getPhoneNumber(subId,
+                    SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, callingPackage,
+                    callingFeatureId);
+            if (!TextUtils.isEmpty(numberFromCarrier)) {
+                return numberFromCarrier;
+            }
+            String numberFromUicc = getPhoneNumber(
+                    subId, SubscriptionManager.PHONE_NUMBER_SOURCE_UICC, callingPackage,
+                    callingFeatureId);
+            if (!TextUtils.isEmpty(numberFromUicc)) {
+                return numberFromUicc;
+            }
+            String numberFromIms = getPhoneNumber(
+                    subId, SubscriptionManager.PHONE_NUMBER_SOURCE_IMS, callingPackage,
+                    callingFeatureId);
+            if (!TextUtils.isEmpty(numberFromIms)) {
+                return numberFromIms;
+            }
+            return "";
         }
-        String numberFromUicc = getPhoneNumber(
-                subId, SubscriptionManager.PHONE_NUMBER_SOURCE_UICC, callingPackage,
-                callingFeatureId);
-        if (!TextUtils.isEmpty(numberFromUicc)) {
-            return numberFromUicc;
-        }
-        String numberFromIms = getPhoneNumber(
-                subId, SubscriptionManager.PHONE_NUMBER_SOURCE_IMS, callingPackage,
-                callingFeatureId);
-        if (!TextUtils.isEmpty(numberFromIms)) {
-            return numberFromIms;
-        }
-        return "";
     }
 
     /**
@@ -4153,6 +4314,10 @@
                     SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_DATABASE_UPDATED)) {
                 logl("Sim specific settings changed the database.");
                 mSubscriptionDatabaseManager.reloadDatabaseSync();
+                if (mFeatureFlags.backupAndRestoreForEnable2g()) {
+                    Arrays.stream(PhoneFactory.getPhones())
+                            .forEach(Phone::loadAllowedNetworksFromSubscriptionDatabase);
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -4417,13 +4582,49 @@
     public List<String> getSatelliteEntitlementPlmnList(int subId) {
         SubscriptionInfoInternal subInfo = mSubscriptionDatabaseManager.getSubscriptionInfoInternal(
                 subId);
-        if (subInfo == null) {
-            loge("getSatelliteEntitlementPlmnList: invalid subId=" + subId);
-            return new ArrayList<>();
+
+        return Optional.ofNullable(subInfo)
+                .map(SubscriptionInfoInternal::getSatelliteEntitlementPlmns)
+                .filter(s -> !s.isEmpty())
+                .map(s -> Arrays.stream(s.split(",")).collect(Collectors.toList()))
+                .orElse(new ArrayList<>());
+    }
+
+    /**
+     * checks whether esim bootstrap is activated for any of the available active subscription info
+     * list.
+     *
+     * @return {@code true} if esim bootstrap is activated for any of the active subscription,
+     * else {@code false}
+     *
+     */
+    public boolean isEsimBootStrapProvisioningActivated() {
+        if (!mFeatureFlags.esimBootstrapProvisioningFlag()) {
+            return false;
         }
 
-        return Arrays.stream(subInfo.getSatelliteEntitlementPlmns().split(",")).collect(
-                Collectors.toList());
+        List<SubscriptionInfo> activeSubInfos =
+                getActiveSubscriptionInfoList(mContext.getOpPackageName(),
+                        mContext.getAttributionTag(), true/*isForAllProfile*/);
+
+        return activeSubInfos.stream().anyMatch(subInfo -> subInfo != null
+                && subInfo.getProfileClass() == SubscriptionManager.PROFILE_CLASS_PROVISIONING);
+    }
+
+    /**
+     * checks whether esim bootstrap is activated for the subscription.
+     *
+     * @return {@code true} if esim bootstrap is activated for sub id else {@code false}
+     *
+     */
+    public boolean isEsimBootStrapProvisioningActiveForSubId(int subId) {
+        if (!mFeatureFlags.esimBootstrapProvisioningFlag()) {
+            return false;
+        }
+
+        SubscriptionInfoInternal subInfo = getSubscriptionInfoInternal(subId);
+        return subInfo != null
+                && subInfo.getProfileClass() == SubscriptionManager.PROFILE_CLASS_PROVISIONING;
     }
 
     /**
@@ -4527,6 +4728,11 @@
                     "config_satellite_sim_spn_identifier", "");
         }
         log("isSatelliteSpn: overlaySpn=" + overlaySpn + ", spn=" + spn);
+
+        if (TextUtils.isEmpty(spn) || TextUtils.isEmpty(overlaySpn)) {
+            return false;
+        }
+
         return TextUtils.equals(spn, overlaySpn);
     }
 
@@ -4537,6 +4743,24 @@
     }
 
     /**
+     * Iterates through previously subscribed SIMs to excludes subscriptions that are not visible
+     * to the users to provide a more appropriate number to describe the current SIM.
+     * @param subId current subscription id.
+     * @return cardNumber subId excluding invisible subscriptions.
+     */
+    private int getCardNumber(int subId) {
+        int cardNumber = subId; // Initialize with the potential card number
+        for (int i = subId - 1; i > 0; i--) {
+            SubscriptionInfoInternal subInfo = getSubscriptionInfoInternal(i);
+            if (subInfo != null && !subInfo.isVisible()) {
+                cardNumber--;
+            }
+        }
+
+        return cardNumber;
+    }
+
+    /**
      * Log debug messages.
      *
      * @param s debug messages
diff --git a/src/java/com/android/internal/telephony/uicc/PinStorage.java b/src/java/com/android/internal/telephony/uicc/PinStorage.java
index 23769ad..e4a0cfa 100644
--- a/src/java/com/android/internal/telephony/uicc/PinStorage.java
+++ b/src/java/com/android/internal/telephony/uicc/PinStorage.java
@@ -193,9 +193,11 @@
 
         CarrierConfigManager ccm = mContext.getSystemService(CarrierConfigManager.class);
         // Callback directly handle config change and should be executed in 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));
+        }
 
         // Initialize the long term secret key. This needs to be present in all cases:
         //  - if the device is not secure or is locked: key does not require user authentication
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
index 0459bf6..84e84d9 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccController.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -1799,6 +1799,8 @@
         }
         pw.decreaseIndent();
         pw.println();
+        mCarrierServiceBindHelper.dump(fd, pw, args);
+        pw.println();
         pw.println("sLocalLog= ");
         pw.increaseIndent();
         mPinStorage.dump(fd, pw, args);
diff --git a/src/java/com/android/internal/telephony/uicc/UiccPort.java b/src/java/com/android/internal/telephony/uicc/UiccPort.java
index fd8f1c4..9e341ef 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccPort.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccPort.java
@@ -31,6 +31,7 @@
 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;
@@ -57,8 +58,9 @@
     private int mPortIdx;
     private int mPhysicalSlotIndex;
 
-    // The list of the opened logical channel record. The channels will be closed by us when
-    // detecting client died without closing them in advance.
+    // The list of the opened logical channel record.
+    // The channels will be closed by us when detecting client died without closing them in advance.
+    // The same lock should be used to protect both access of the list and the individual record.
     @GuardedBy("mOpenChannelRecords")
     private final List<OpenLogicalChannelRecord> mOpenChannelRecords = new ArrayList<>();
 
@@ -101,11 +103,13 @@
             }
             mUiccProfile = null;
         }
+        cleanupOpenLogicalChannelRecordsIfNeeded();
     }
 
     @Override
     protected void finalize() {
         if (DBG) log("UiccPort finalized");
+        cleanupOpenLogicalChannelRecordsIfNeeded();
     }
 
     /**
@@ -389,8 +393,10 @@
     public void onLogicalChannelOpened(@NonNull IccLogicalChannelRequest request) {
         OpenLogicalChannelRecord record = new OpenLogicalChannelRecord(request);
         try {
-            request.binder.linkToDeath(record, /*flags=*/ 0);
-            addOpenLogicalChannelRecord(record);
+            synchronized (mOpenChannelRecords) {
+                request.binder.linkToDeath(record, /*flags=*/ 0);
+                mOpenChannelRecords.add(record);
+            }
             if (DBG) log("onLogicalChannelOpened: monitoring client " + record);
         } catch (RemoteException | NullPointerException ex) {
             loge("IccOpenLogicChannel client has died, clean up manually");
@@ -406,11 +412,13 @@
      */
     public void onLogicalChannelClosed(int channelId) {
         OpenLogicalChannelRecord record = getOpenLogicalChannelRecord(channelId);
-        if (record != null && record.mRequest != null && record.mRequest.binder != null) {
-            if (DBG) log("onLogicalChannelClosed: stop monitoring client " + record);
-            record.mRequest.binder.unlinkToDeath(record, /*flags=*/ 0);
-            removeOpenLogicalChannelRecord(record);
-            record.mRequest.binder = null;
+        synchronized (mOpenChannelRecords) {
+            if (record != null && record.mRequest != null && record.mRequest.binder != null) {
+                if (DBG) log("onLogicalChannelClosed: stop monitoring client " + record);
+                record.mRequest.binder.unlinkToDeath(record, /*flags=*/ 0);
+                record.mRequest.binder = null;
+                mOpenChannelRecords.remove(record);
+            }
         }
     }
 
@@ -428,15 +436,21 @@
         return null;
     }
 
-    private void addOpenLogicalChannelRecord(OpenLogicalChannelRecord record) {
-        synchronized (mOpenChannelRecords) {
-            mOpenChannelRecords.add(record);
-        }
-    }
-
-    private void removeOpenLogicalChannelRecord(OpenLogicalChannelRecord record) {
-        synchronized (mOpenChannelRecords) {
-            mOpenChannelRecords.remove(record);
+    /**
+     * Clean up records when logical channels underneath have been released, in cases like SIM
+     * removal or modem reset. The obsoleted records may trigger a redundant release of logical
+     * 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();
+            }
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/uicc/UiccProfile.java b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
index 0457971..a22f075 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccProfile.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccProfile.java
@@ -1290,6 +1290,11 @@
             return -1;
         }
 
+        if (mUiccApplications[index] == null) {
+            loge("App index " + index + " is invalid");
+            return -1;
+        }
+
         if (mUiccApplications[index].getType() != expectedAppType
                 && mUiccApplications[index].getType() != altExpectedAppType) {
             loge("App index " + index + " is invalid since it's not "
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java b/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java
index 3bd66f8..7bdec47 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/EuiccPort.java
@@ -133,7 +133,8 @@
             UiccCard card, MultipleEnabledProfilesMode supportedMepMode) {
         super(c, ci, ics, phoneId, lock, card);
         // TODO: Set supportExtendedApdu based on ATR.
-        mApduSender = new ApduSender(ci, ISD_R_AID, false /* supportExtendedApdu */);
+        mApduSender = new ApduSender(c, phoneId, ci, ISD_R_AID,
+                              false /* supportExtendedApdu */);
         if (TextUtils.isEmpty(ics.eid)) {
             loge("no eid given in constructor for phone " + phoneId);
         } else {
diff --git a/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java b/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
index 8e7237e..f42d5a2 100644
--- a/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
+++ b/src/java/com/android/internal/telephony/uicc/euicc/apdu/ApduSender.java
@@ -17,9 +17,13 @@
 package com.android.internal.telephony.uicc.euicc.apdu;
 
 import android.annotation.Nullable;
+import android.content.Context;
+import android.content.SharedPreferences;
 import android.os.Handler;
 import android.os.Looper;
+import android.preference.PreferenceManager;
 import android.telephony.IccOpenLogicalChannelResponse;
+import android.util.Base64;
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.uicc.IccIoResult;
@@ -30,6 +34,7 @@
 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
@@ -52,6 +57,9 @@
     private static final int SW1_NO_ERROR = 0x91;
 
     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";
+    private static final String CHANNEL_RESPONSE_ID_PRE = "esim-res-id";
 
     private static void logv(String msg) {
         Rlog.v(LOG_TAG, msg);
@@ -66,6 +74,9 @@
     private final OpenLogicalChannelInvocation mOpenChannel;
     private final CloseLogicalChannelInvocation mCloseChannel;
     private final TransmitApduLogicalChannelInvocation mTransmitApdu;
+    private final Context mContext;
+    private final String mChannelKey;
+    private final String mChannelResponseKey;
 
     // Lock for accessing mChannelOpened. We only allow to open a single logical channel at any
     // time for an AID.
@@ -75,12 +86,17 @@
     /**
      * @param aid The AID that will be used to open a logical channel to.
      */
-    public ApduSender(CommandsInterface ci, String aid, boolean supportExtendedApdu) {
+    public ApduSender(Context context, int phoneId, CommandsInterface ci, String aid,
+            boolean supportExtendedApdu) {
         mAid = aid;
+        mContext = context;
         mSupportExtendedApdu = supportExtendedApdu;
         mOpenChannel = new OpenLogicalChannelInvocation(ci);
         mCloseChannel = new CloseLogicalChannelInvocation(ci);
         mTransmitApdu = new TransmitApduLogicalChannelInvocation(ci);
+        mChannelKey = CHANNEL_ID_PRE + "_" + phoneId;
+        mChannelResponseKey = CHANNEL_RESPONSE_ID_PRE + "_" + phoneId;
+        closeExistingChannelIfExists();
     }
 
     /**
@@ -129,6 +145,20 @@
             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) {
@@ -143,8 +173,15 @@
 
                 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(openChannelResponse.getSelectResponse(), builder);
+                    requestProvider.buildRequest(selectResponse, builder);
                 } catch (Throwable e) {
                     requestException = e;
                 }
@@ -223,7 +260,7 @@
             AsyncResultCallback<IccIoResult> resultCallback,
             Handler handler) {
         ByteArrayOutputStream resultBuilder =
-                responseBuilder == null ? new ByteArrayOutputStream() : responseBuilder;
+            responseBuilder == null ? new ByteArrayOutputStream() : responseBuilder;
         if (lastResponse.payload != null) {
             try {
                 resultBuilder.write(lastResponse.payload);
@@ -267,6 +304,12 @@
             @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();
                 }
@@ -279,4 +322,39 @@
             }
         }, handler);
     }
+
+    /**
+     * Cleanup the existing opened channel which was remainined opened earlier due
+     * to failure or crash.
+     */
+    private void closeExistingChannelIfExists() {
+        if (mCloseChannel != null) {
+            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();
+                }
+                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());
+            }
+        }
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
index 2a66a5f..e06e4fe 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierAppUtilsTest.java
@@ -23,6 +23,7 @@
 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;
@@ -34,9 +35,12 @@
 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;
@@ -59,6 +63,9 @@
     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;
@@ -79,6 +86,7 @@
 
     @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());
@@ -113,6 +121,7 @@
     public void testDisableCarrierAppsUntilPrivileged_EmptyList() {
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, new ArraySet<>(), ASSOCIATED_APPS, mContext);
+
         Mockito.verifyNoMoreInteractions(mPackageManager, mTelephonyManager);
     }
 
@@ -125,9 +134,11 @@
                         | PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(null);
         ArraySet<String> systemCarrierAppsDisabledUntilUsed = new ArraySet<>();
         systemCarrierAppsDisabledUntilUsed.add("com.example.missing.app");
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, systemCarrierAppsDisabledUntilUsed, ASSOCIATED_APPS,
                 mContext);
+
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN));
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -147,8 +158,10 @@
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS))
                 .thenReturn(appInfo);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN));
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -173,15 +186,16 @@
         Mockito.when(mPackageManager
                 .getApplicationEnabledSetting(Mockito.anyString()))
                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
-
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -199,15 +213,16 @@
         Mockito.when(mPackageManager
                 .getApplicationEnabledSetting(Mockito.anyString()))
                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
-
         Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -231,8 +246,10 @@
                         | PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -255,11 +272,12 @@
         Mockito.when(mPackageManager
                 .getApplicationEnabledSetting(Mockito.anyString()))
                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
-
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -287,8 +305,10 @@
                 .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
@@ -329,8 +349,10 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
@@ -371,8 +393,10 @@
                 .thenReturn(null);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
@@ -412,8 +436,10 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
@@ -440,8 +466,10 @@
                 .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -465,9 +493,11 @@
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(appInfo);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
                 ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -476,7 +506,7 @@
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
-    /** Configured app has no privileges, and was uninstalled - should do nothing. */
+    /** Configured app has no privileges, and was explicitly disabled - should do nothing. */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_Disabled() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
@@ -492,8 +522,10 @@
                 .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -502,7 +534,7 @@
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
-    /** Telephony is not initialized, and app was uninstalled - should do nothing. */
+    /** Telephony is not initialized, and app was explicitly disabled - should do nothing. */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NullPrivileges_Disabled() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
@@ -516,9 +548,11 @@
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(appInfo);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
                 ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -527,7 +561,7 @@
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
-    /** Configured app has no privileges, and is explicitly installed - should do nothing. */
+    /** Configured app has no privileges, and was explicitly enabled - should do nothing. */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_Enabled() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
@@ -543,8 +577,10 @@
                 .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -553,7 +589,7 @@
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
-    /** Telephony is not initialized, and app is explicitly installed - should do nothing. */
+    /** Telephony is not initialized, and app was explicitly enabled - should do nothing. */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NullPrivileges_Enabled() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
@@ -567,9 +603,11 @@
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(appInfo);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
                 ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -578,7 +616,8 @@
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
-    /** Configured /data app has no privileges - should do nothing. */
+    /** Configured app has been installed as an update in /data and has no privileges
+     *  - should do nothing. */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_UpdatedApp() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
@@ -595,8 +634,10 @@
                 .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -620,9 +661,11 @@
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(appInfo);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
                 ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -632,7 +675,8 @@
     }
 
     /**
-     * Configured app has no privileges, and is in the default state - should uninstalled.
+     * Configured app has no privileges, is in the default state and it was never uninstalled before
+     *  - should uninstalled.
      * Associated app is installed and should not be touched.
      */
     @Test @SmallTest
@@ -660,8 +704,10 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
@@ -675,8 +721,8 @@
     }
 
     /**
-     * Configured app has no privileges, and is in the default state along with associated app -
-     * should uninstall both.
+     * Configured app has no privileges, is in the default state along with associated app and never
+     * unstalled before - should uninstall both.
      */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_Associated_Default()
@@ -703,8 +749,10 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
@@ -717,7 +765,7 @@
 
     /**
      * Configured app has no privileges, and is in the default state along with associated app, and
-     * disabling has already occurred - should only uninstall configured app.
+     * disabling has already occurred - should uninstall nothing.
      */
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NoPrivileges_Associated_Default_HandledSdk()
@@ -747,15 +795,17 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Different associated app SDK than usual.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS,
                 makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT), mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
-        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_INSTALLED);
@@ -765,7 +815,7 @@
 
     /**
      * Configured app has no privileges, and is in the default state along with associated app, and
-     * disabling has already occurred - should only uninstall configured app.
+     * disabling has already occurred - should uninstall nothing.
      */
     @Test @SmallTest
     public void testDCAUP_NoPrivileges_Associated_Default_HandledSdk_AssociatedSdkUnspecified()
@@ -795,14 +845,16 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Using SDK_UNSPECIFIED for the associated app's addedInSdk.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
-        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_INSTALLED);
@@ -812,15 +864,14 @@
 
     /**
      * Configured app has no privileges, and is in the default state along with associated app, and
-     * disabling has not yet occurred on this SDK level - should uninstall both since the associated
-     * app's SDK matches.
+     * disabling has not yet occurred on any SDK level - should uninstall both since disabling never
+     * occurred before and the associated app's SDK matches.
      */
     @Test @SmallTest
     public void testDCAUP_NoPrivileges_Associated_Default_NewSdk_AssociatedSdkCurrent()
             throws Exception {
         Settings.Secure.putIntForUser(
-                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED,
-                Build.VERSION.SDK_INT - 1, USER_ID);
+                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, USER_ID);
         ApplicationInfo appInfo = new ApplicationInfo();
         appInfo.packageName = CARRIER_APP;
         appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
@@ -843,10 +894,12 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Different associated app SDK than usual.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS,
                 makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT), mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
@@ -861,7 +914,7 @@
 
     /**
      * Configured app has no privileges, and is in the default state along with associated app, and
-     * disabling has not yet occurred on the current SDK - should only uninstall configured app
+     * disabling has not yet occurred on the current SDK - should uninstall nothing
      * since the associated app's SDK isn't specified but we've already run at least once.
      */
     @Test @SmallTest
@@ -892,14 +945,16 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Using SDK_UNSPECIFIED for the associated app's addedInSdk.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
-        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_INSTALLED);
@@ -909,7 +964,7 @@
 
     /**
      * Configured app has no privileges, and is in the default state along with associated app, and
-     * disabling has not yet occurred on the current SDK - should only uninstall configured app
+     * disabling has not yet occurred on the current SDK - should uninstall nothing
      * since the associated app's SDK doesn't match.
      */
     @Test @SmallTest
@@ -940,16 +995,18 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Different associated app SDK than usual.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS,
                 makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT - 1),
                 mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
-        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_INSTALLED);
@@ -959,8 +1016,8 @@
 
     /**
      * Configured app has no privileges, and is in the default state along with associated app, and
-     * disabling has not yet occurred on this SDK level - should uninstall both since the associated
-     * app's SDK is newer than the last evaluation.
+     * disabling has not yet occurred on this SDK level - should uninstall only associated
+     * app since the associated app's SDK is newer than the last evaluation.
      *
      * While this case is expected to feel somewhat strange, it effectively simulates skipping a
      * whole SDK level in a single OTA. For example, the device is on P. A new associated app is
@@ -996,16 +1053,18 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Different associated app SDK than usual.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS,
                 makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT - 1),
                 mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
-        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(ASSOCIATED_APP,
                 PackageManager.SYSTEM_APP_STATE_INSTALLED);
@@ -1043,10 +1102,12 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Different associated app SDK than usual.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS,
                 makeAssociatedApp(CARRIER_APP, ASSOCIATED_APP, Build.VERSION.SDK_INT), mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
@@ -1089,9 +1150,11 @@
                 .thenReturn(associatedAppInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         // Using SDK_UNSPECIFIED for the associated app's addedInSdk.
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(ASSOCIATED_APP,
@@ -1104,7 +1167,10 @@
                 PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
     }
 
-    /** Telephony is not initialized, and app is in the default state - should uninstall it. */
+    /**
+     * Telephony is not initialized, and app is in the default state and never uninstall before
+     * - should uninstall it.
+     **/
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NullPrivileges_Default() throws Exception {
         ApplicationInfo appInfo = new ApplicationInfo();
@@ -1118,16 +1184,51 @@
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(appInfo);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
                 ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
     }
 
-    /** Configured app has no privileges, and is disabled until used or not installed - should do
+    /**
+     * Telephony is not initialized, and app is in the default state but uninstall before
+     * - should not uninstall again.
+     **/
+    @Test @SmallTest
+    public void testDisableCarrierAppsUntilPrivileged_NullPrivileges_Default_alreadyUninstalled()
+            throws Exception {
+        Settings.Secure.putIntForUser(
+                mContentResolver, Settings.Secure.CARRIER_APPS_HANDLED,
+                Build.VERSION.SDK_INT, USER_ID);
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.packageName = CARRIER_APP;
+        appInfo.flags |= ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_INSTALLED;
+        Mockito.when(mPackageManager
+                .getApplicationEnabledSetting(Mockito.anyString()))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT);
+        Mockito.when(mPackageManager.getApplicationInfo(CARRIER_APP,
+                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+                        | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+                        | PackageManager.MATCH_SYSTEM_ONLY))
+                .thenReturn(appInfo);
+
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
+                null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
+                ASSOCIATED_APPS, mContext);
+
+        Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
+        Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(CARRIER_APP,
+                PackageManager.SYSTEM_APP_STATE_UNINSTALLED);
+    }
+
+    /**
+     * Configured app has no privileges, and is disabled until used or not installed - should do
      *  nothing.
      **/
     @Test @SmallTest
@@ -1146,8 +1247,10 @@
                 .thenReturn(appInfo);
         Mockito.when(mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(CARRIER_APP))
                 .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE, mTelephonyManager,
                 mContentResolver, USER_ID, CARRIER_APPS, ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
@@ -1156,8 +1259,9 @@
                 Mockito.eq(PackageManager.SYSTEM_APP_STATE_UNINSTALLED));
     }
 
-    /** Telephony is not initialized, and app is disabled until used or not installed - should do
-     *  nothing.
+    /**
+     * Telephony is not initialized, and app is disabled until used or not installed - should do
+     * nothing.
      **/
     @Test @SmallTest
     public void testDisableCarrierAppsUntilPrivileged_NullPrivileges_DisabledUntilUsed()
@@ -1173,9 +1277,11 @@
                         | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
                         | PackageManager.MATCH_SYSTEM_ONLY))
                 .thenReturn(appInfo);
+
         CarrierAppUtils.disableCarrierAppsUntilPrivileged(CALLING_PACKAGE,
                 null /* telephonyManager */, mContentResolver, USER_ID, CARRIER_APPS,
                 ASSOCIATED_APPS, mContext);
+
         Mockito.verify(mPackageManager).setSystemAppState(CARRIER_APP,
                 PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN);
         Mockito.verify(mPackageManager, Mockito.never()).setSystemAppState(Mockito.anyString(),
diff --git a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
index 07482e0..e60e95b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/CarrierKeyDownloadMgrTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.times;
@@ -31,6 +32,8 @@
 import android.app.DownloadManager;
 import android.content.Context;
 import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Network;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ImsiEncryptionInfo;
@@ -38,18 +41,18 @@
 import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.text.TextUtils;
 import android.util.Pair;
 
 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.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
 import org.mockito.Mockito;
 
 import java.security.PublicKey;
@@ -91,7 +94,7 @@
                     + "\"public-key\": \"" + CERT + "\"}]}";
 
     private CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener;
-    private FeatureFlags mFeatureFlags;
+
     @Before
     public void setUp() throws Exception {
         logd("CarrierActionAgentTest +Setup!");
@@ -99,15 +102,18 @@
         mBundle = mContextFixture.getCarrierConfigBundle();
         when(mCarrierConfigManager.getConfigForSubId(anyInt(), any())).thenReturn(mBundle);
         when(mUserManager.isUserUnlocked()).thenReturn(true);
+        when(mKeyguardManager.isDeviceLocked()).thenReturn(false);
         // Capture listener to emulate the carrier config change notification used later
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
                 ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
-        mFeatureFlags = Mockito.mock(FeatureFlags.class);
-        mCarrierKeyDM = new CarrierKeyDownloadManager(mPhone, mFeatureFlags);
+        mCarrierKeyDM = new CarrierKeyDownloadManager(mPhone);
         verify(mCarrierConfigManager).registerCarrierConfigChangeListener(any(),
                 listenerArgumentCaptor.capture());
         mCarrierConfigChangeListener = listenerArgumentCaptor.getAllValues().get(0);
-
+        mConnectivityManager = (ConnectivityManager) mPhone.getContext().getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+        Network network = Mockito.mock(Network.class);
+        when(mConnectivityManager.getActiveNetwork()).thenReturn(network);
         processAllMessages();
         logd("CarrierActionAgentTest -Setup!");
     }
@@ -331,8 +337,8 @@
         expectedCal.add(Calendar.DATE, 1);
         String dateExpected = dt.format(expectedCal.getTime());
 
-        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
-        when(mTelephonyManager.getSimCarrierId()).thenReturn(1);
+        when(mPhone.getOperatorNumeric()).thenReturn("310260");
+        when(mPhone.getCarrierId()).thenReturn(1);
         Intent mIntent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
         mIntent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId);
         mContext.sendBroadcast(mIntent);
@@ -355,11 +361,11 @@
         bundle.putInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT, 3);
         bundle.putString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING, mURL);
 
-        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
-        when(mTelephonyManager.getSimCarrierId()).thenReturn(1);
+        when(mPhone.getOperatorNumeric()).thenReturn("310260");
+        when(mPhone.getCarrierId()).thenReturn(1);
         mCarrierConfigChangeListener.onCarrierConfigChanged(0 /* slotIndex */,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+                1, TelephonyManager.UNKNOWN_CARRIER_ID);
         processAllMessages();
         assertEquals("310260", mCarrierKeyDM.mMccMncForDownload);
         assertEquals(1, mCarrierKeyDM.mCarrierId);
@@ -369,6 +375,7 @@
     @SmallTest
     public void testCarrierConfigChangedWithUserLocked() {
         when(mUserManager.isUserUnlocked()).thenReturn(false);
+        when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
         CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
         int slotId = mPhone.getPhoneId();
@@ -376,14 +383,14 @@
         bundle.putInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT, 3);
         bundle.putString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING, mURL);
 
-        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
-        when(mTelephonyManager.getSimCarrierId()).thenReturn(1);
+        when(mPhone.getOperatorNumeric()).thenReturn("310260");
+        when(mPhone.getCarrierId()).thenReturn(1);
         mCarrierConfigChangeListener.onCarrierConfigChanged(0 /* slotIndex */,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+                1, TelephonyManager.UNKNOWN_CARRIER_ID);
         processAllMessages();
-        assertNull(mCarrierKeyDM.mMccMncForDownload);
-        assertEquals(0, mCarrierKeyDM.mCarrierId);
+        assertEquals("310260", mCarrierKeyDM.mMccMncForDownload);
+        assertEquals(1, mCarrierKeyDM.mCarrierId);
     }
 
     @Test
@@ -391,6 +398,7 @@
     public void testUserLockedAfterCarrierConfigChanged() {
         // User is locked at beginning
         when(mUserManager.isUserUnlocked()).thenReturn(false);
+        when(mKeyguardManager.isDeviceLocked()).thenReturn(true);
         CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
         int slotId = mPhone.getPhoneId();
@@ -399,17 +407,18 @@
         bundle.putString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING, mURL);
 
         // Carrier config change received
-        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
-        when(mTelephonyManager.getSimCarrierId()).thenReturn(1);
+        when(mPhone.getOperatorNumeric()).thenReturn("310260");
+        when(mPhone.getCarrierId()).thenReturn(1);
         mCarrierConfigChangeListener.onCarrierConfigChanged(0 /* slotIndex */,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+                1, TelephonyManager.UNKNOWN_CARRIER_ID);
         processAllMessages();
 
         // User unlocked event received
-        Intent mIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
+        Intent mIntent = new Intent(Intent.ACTION_USER_PRESENT);
         mContext.sendBroadcast(mIntent);
         when(mUserManager.isUserUnlocked()).thenReturn(true);
+        when(mKeyguardManager.isDeviceLocked()).thenReturn(false);
         processAllMessages();
 
         assertEquals("310260", mCarrierKeyDM.mMccMncForDownload);
@@ -432,11 +441,11 @@
 
         mCarrierConfigChangeListener.onCarrierConfigChanged(0 /* slotIndex */,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID,
-                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+                1, TelephonyManager.UNKNOWN_CARRIER_ID);
         processAllMessages();
-        assertNull(mCarrierKeyDM.mMccMncForDownload);
+        assertTrue(TextUtils.isEmpty(mCarrierKeyDM.mMccMncForDownload));
 
-        verify(mPhone).deleteCarrierInfoForImsiEncryption(0);
+        verify(mPhone).deleteCarrierInfoForImsiEncryption(1, "");
     }
 
     /**
@@ -453,8 +462,8 @@
         bundle.putInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT, 3);
         bundle.putString(CarrierConfigManager.IMSI_KEY_DOWNLOAD_URL_STRING, mURL);
 
-        when(mTelephonyManager.getSimOperator(anyInt())).thenReturn("310260");
-        when(mTelephonyManager.getSimCarrierId()).thenReturn(1);
+        when(mPhone.getOperatorNumeric()).thenReturn("310260");
+        when(mPhone.getCarrierId()).thenReturn(1);
         Intent mIntent = new Intent("com.android.internal.telephony.carrier_key_download_alarm");
         mIntent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, slotIndex);
         mContext.sendBroadcast(mIntent);
@@ -493,4 +502,4 @@
         assertEquals(CarrierKeyDownloadManager
                 .cleanCertString("Comments before" + CERT + "Comments after"), CERT);
     }
-}
+}
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java
index 0bce5cb..329b0b8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ConnectionTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -25,9 +26,14 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import java.util.ArrayList;
+
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.telephony.emergency.EmergencyNumber;
 
+import com.android.internal.telephony.PhoneInternalInterface.DialArgs;
 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
 
 import org.junit.After;
@@ -150,7 +156,7 @@
         assertNull(connection1.getEmergencyNumberInfo());
         assertFalse(connection1.hasKnownUserIntentEmergency());
 
-        connection2.setEmergencyCallInfo(mPhone.getCallTracker());
+        connection2.setEmergencyCallInfo(mPhone.getCallTracker(), null);
         connection2.setHasKnownUserIntentEmergency(true);
         connection1.migrateFrom(connection2);
 
@@ -164,7 +170,7 @@
     @Test
     public void testEmergencyCallParameters() {
         Connection connection = new TestConnection(TEST_PHONE_TYPE);
-        connection.setEmergencyCallInfo(mPhone.getCallTracker());
+        connection.setEmergencyCallInfo(mPhone.getCallTracker(), null);
         assertTrue(connection.isEmergencyCall());
         assertEquals(getTestEmergencyNumber(), connection.getEmergencyNumberInfo());
         connection.setHasKnownUserIntentEmergency(true);
@@ -186,9 +192,44 @@
                 .thenReturn(getTestEmergencyNumber());
 
         //Ensure the connection is considered as an emergency call:
-        mTestConnection.setEmergencyCallInfo(mCT);
+        mTestConnection.setEmergencyCallInfo(mCT, null);
         assertTrue(mTestConnection.isEmergencyCall());
     }
 
+    @Test
+    public void testUpdateEmergencyRouting() {
+        DialArgs dialArgs = new DialArgs.Builder().build();
+        Connection connection = new TestConnection(TEST_PHONE_TYPE);
+        connection.setEmergencyCallInfo(mPhone.getCallTracker(), dialArgs);
+
+        // Not updated when DomainSelectionService is disabled.
+        assertEquals(getTestEmergencyNumber(), connection.getEmergencyNumberInfo());
+
+        // Enable DomainSelectionService
+        doReturn(true).when(mDomainSelectionResolver).isDomainSelectionSupported();
+        connection = new TestConnection(TEST_PHONE_TYPE);
+        connection.setEmergencyCallInfo(mPhone.getCallTracker(), dialArgs);
+
+        // Not updated when IS_EMERGENCY_ROUTING is not specified.
+        assertEquals(getTestEmergencyNumber(), connection.getEmergencyNumberInfo());
+
+        Bundle extras = new Bundle();
+        extras.putBoolean(PhoneConstants.EXTRA_USE_EMERGENCY_ROUTING, true);
+        extras.putInt(PhoneConstants.EXTRA_EMERGENCY_SERVICE_CATEGORY,
+                EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE);
+        dialArgs = new DialArgs.Builder().setIntentExtras(extras).build();
+
+        connection = new TestConnection(TEST_PHONE_TYPE);
+        connection.setEmergencyCallInfo(mPhone.getCallTracker(), 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 DomainSelectionService is enabled.
+        assertNotEquals(getTestEmergencyNumber(), connection.getEmergencyNumberInfo());
+        assertEquals(expectedNumber, connection.getEmergencyNumberInfo());
+    }
+
     // TODO Verify more methods in Connection
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index bef8944..4612ad9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -65,6 +65,7 @@
 import android.net.wifi.WifiManager;
 import android.os.BatteryManager;
 import android.os.Bundle;
+import android.os.DropBoxManager;
 import android.os.Handler;
 import android.os.IInterface;
 import android.os.PersistableBundle;
@@ -313,6 +314,8 @@
                     return mImsManager;
                 case Context.DEVICE_POLICY_SERVICE:
                     return mDevicePolicyManager;
+                case Context.DROPBOX_SERVICE:
+                    return mDropBoxManager;
                 default:
                     return null;
             }
@@ -362,6 +365,10 @@
                 return Context.ALARM_SERVICE;
             } else if (serviceClass == DevicePolicyManager.class) {
                 return Context.DEVICE_POLICY_SERVICE;
+            } else if (serviceClass == NotificationManager.class) {
+                return Context.NOTIFICATION_SERVICE;
+            } else if (serviceClass == DropBoxManager.class) {
+                return Context.DROPBOX_SERVICE;
             }
             return super.getSystemServiceName(serviceClass);
         }
@@ -737,6 +744,7 @@
     private final NetworkPolicyManager mNetworkPolicyManager = mock(NetworkPolicyManager.class);
     private final ImsManager mImsManager = mock(ImsManager.class);
     private final DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class);
+    private final DropBoxManager mDropBoxManager = mock(DropBoxManager.class);
     private final Configuration mConfiguration = new Configuration();
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
     private final SharedPreferences mSharedPreferences = PreferenceManager
diff --git a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
index d27ab98..8209f92 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/DefaultPhoneNotifierTest.java
@@ -392,4 +392,13 @@
         verify(mTelephonyRegistryManager).notifySimultaneousCellularCallingSubscriptionsChanged(
                 eq(subs));
     }
+
+    @Test
+    @SmallTest
+    public void testCarrierRoamingNtnModeChanged() {
+        int subId = mPhone.getSubId();
+        mDefaultPhoneNotifierUT.notifyCarrierRoamingNtnModeChanged(mPhone, true);
+        verify(mTelephonyRegistryManager).notifyCarrierRoamingNtnModeChanged(
+                eq(subId), eq(true));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index 9f96ce4..ba08f8b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -2712,6 +2712,9 @@
         doReturn(true).when(dsResolver).isDomainSelectionSupported();
         DomainSelectionResolver.setDomainSelectionResolver(dsResolver);
 
+        EmergencyStateTracker est = Mockito.mock(EmergencyStateTracker.class);
+        replaceInstance(EmergencyStateTracker.class, "INSTANCE", null, est);
+
         mPhoneUT.handleMessage(mPhoneUT.obtainMessage(
                 GsmCdmaPhone.EVENT_EMERGENCY_CALLBACK_MODE_ENTER));
 
@@ -2721,7 +2724,8 @@
         mPhoneUT.exitEmergencyCallbackMode();
         processAllMessages();
 
-        verify(mContext, never()).sendStickyBroadcastAsUser(any(), any());
+        // Verify that the request is routed to EmergencyStateTracker.
+        verify(est).exitEmergencyCallbackMode();
     }
 
     @Test
@@ -3015,7 +3019,63 @@
         processAllMessages();
 
         verify(mNullCipherNotifier, times(1))
-                .onSecurityAlgorithmUpdate(eq(mContext), eq(0), eq(update));
+                .onSecurityAlgorithmUpdate(eq(mContext), eq(0), eq(0), eq(update));
+    }
+
+    @Test
+    public void testUpdateNullCipherNotifier_flagDisabled() {
+        when(mFeatureFlags.enableModemCipherTransparencyUnsolEvents()).thenReturn(false);
+        Phone phoneUT = makeNewPhoneUT();
+        phoneUT.sendMessage(
+                mPhoneUT.obtainMessage(
+                        Phone.EVENT_SUBSCRIPTIONS_CHANGED,
+                        new AsyncResult(null, null, null)));
+        processAllMessages();
+
+        verify(mNullCipherNotifier, never()).setSubscriptionMapping(any(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void testUpdateNullCipherNotifier_activeSubscription() {
+        when(mFeatureFlags.enableModemCipherTransparencyUnsolEvents()).thenReturn(true);
+
+        int subId = 10;
+        SubscriptionInfoInternal subInfo = new SubscriptionInfoInternal.Builder().setSimSlotIndex(
+                0).setId(subId).build();
+        when(mSubscriptionManagerService.getSubscriptionInfoInternal(subId)).thenReturn(
+                subInfo);
+        doReturn(subId).when(mSubscriptionManagerService)
+                .getSubId(anyInt());
+        Phone phoneUT = makeNewPhoneUT();
+
+        phoneUT.sendMessage(
+                mPhoneUT.obtainMessage(
+                        Phone.EVENT_SUBSCRIPTIONS_CHANGED,
+                        new AsyncResult(null, null, null)));
+        processAllMessages();
+
+        verify(mNullCipherNotifier, times(1)).setSubscriptionMapping(eq(mContext), eq(0), eq(10));
+    }
+
+    @Test
+    public void testUpdateNullCipherNotifier_inactiveSubscription() {
+        when(mFeatureFlags.enableModemCipherTransparencyUnsolEvents()).thenReturn(true);
+        int subId = 1;
+        SubscriptionInfoInternal subInfo = new SubscriptionInfoInternal.Builder().setSimSlotIndex(
+                -1).setId(subId).build();
+        when(mSubscriptionManagerService.getSubscriptionInfoInternal(subId)).thenReturn(
+                subInfo);
+        doReturn(subId).when(mSubscriptionManagerService)
+                .getSubId(anyInt());
+        Phone phoneUT = makeNewPhoneUT();
+
+        phoneUT.sendMessage(
+                mPhoneUT.obtainMessage(
+                        Phone.EVENT_SUBSCRIPTIONS_CHANGED,
+                        new AsyncResult(null, null, null)));
+        processAllMessages();
+
+        verify(mNullCipherNotifier, times(1)).setSubscriptionMapping(eq(mContext), eq(0), eq(-1));
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
index a7e9604..855a5dc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
@@ -18,10 +18,8 @@
 
 import static android.telephony.TelephonyManager.ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED;
 import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE;
-import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL;
 import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA;
 import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DISMISS;
-import static android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -251,6 +249,8 @@
         bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(anyInt());
 
+        doReturn(true).when(mFeatureFlags).resetPrimarySimDefaultValues();
+
         replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
         // Capture listener to emulate the carrier config change notification used later
         ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
@@ -496,18 +496,9 @@
         sendCarrierConfigChanged(1, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         processAllMessages();
 
-        verify(mSubscriptionManagerService).setDefaultDataSubId(
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        verify(mSubscriptionManagerService).setDefaultSmsSubId(
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-        verify(mSubscriptionManagerService, never()).setDefaultVoiceSubId(anyInt());
-
-        // Verify intent sent to select sub 2 as default for all types.
-        Intent intent = captureBroadcastIntent();
-        assertEquals(ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED, intent.getAction());
-        assertEquals(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL,
-                intent.getIntExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE, -1));
-        assertEquals(2, intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, -1));
+        verify(mSubscriptionManagerService).setDefaultDataSubId(2);
+        verify(mSubscriptionManagerService).setDefaultSmsSubId(2);
+        verify(mSubscriptionManagerService).setDefaultVoiceSubId(2);
     }
 
     @Test
@@ -911,6 +902,27 @@
         // This time user data should be disabled on phone1.
         verify(mDataSettingsManagerMock2).setDataEnabled(
                 TelephonyManager.DATA_ENABLED_REASON_USER, false, PHONE_PACKAGE);
+
+        // Remove and insert back SIM before it's loaded.
+        clearInvocations(mSubscriptionManagerService);
+        markSubscriptionInactive(1/*subid*/);
+        sendCarrierConfigChanged(0/*phoneid*/, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+        verify(mSubscriptionManagerService).setDefaultDataSubId(2);
+
+        // insert it back, but carrier config not loaded yet
+        clearInvocations(mSubscriptionManagerService);
+        setSimSlotIndex(1/*subid*/, 0/*phoneid*/);
+        mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        sendCarrierConfigChanged(0/*phoneid*/, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+        verify(mSubscriptionManagerService, never()).setDefaultDataSubId(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+        // carrier config loaded
+        clearInvocations(mContext);
+        sendCarrierConfigChanged(0/*phoneid*/, 1/*subid*/);
+        verify(mContext).sendBroadcast(any());
     }
 
     @Test
@@ -1007,4 +1019,57 @@
         // Default data is set to sub1
         verify(mSubscriptionManagerService).syncGroupedSetting(1);
     }
+
+    @Test
+    public void testDailogsAndWarnings_WithBootstrapSim() {
+        doReturn(true).when(mFeatureFlags).esimBootstrapProvisioningFlag();
+
+        // Mark sub 2 as inactive.
+        markSubscriptionInactive(2);
+        mMultiSimSettingControllerUT.notifyAllSubscriptionLoaded();
+        sendCarrierConfigChanged(0, 1);
+        processAllMessages();
+
+        // Sub 1 should be default sub silently.
+        verify(mSubscriptionManagerService).setDefaultDataSubId(1);
+        verify(mSubscriptionManagerService).setDefaultVoiceSubId(1);
+        verify(mSubscriptionManagerService).setDefaultSmsSubId(1);
+        verifyDismissIntentSent();
+
+        // Mark sub 2 bootstrap sim as active in phone[1].
+        doReturn(true).when(mSubscriptionManagerService).isEsimBootStrapProvisioningActivated();
+        setSimSlotIndex(2, 1);
+        clearInvocations(mSubscriptionManagerService);
+        clearInvocations(mContext);
+        mSubInfo[2] = new SubscriptionInfoInternal.Builder().setId(2).setSimSlotIndex(1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_PROVISIONING).build();
+        mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        sendCarrierConfigChanged(1, 2);
+        processAllMessages();
+
+        // Taking out SIM 1.
+        clearInvocations(mSubscriptionManagerService);
+        markSubscriptionInactive(1/*subid*/);
+        mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        sendCarrierConfigChanged(0/*phoneid*/, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        processAllMessages();
+
+        // No user selection needed, no intent should be sent for notification
+        verify(mContext, never()).sendBroadcast(any());
+
+        //Insert back sim1 and switch from sub 1 to sub 3 in phone[0].
+        clearInvocations(mSubscriptionManagerService);
+        markSubscriptionInactive(1);
+        setSimSlotIndex(3, 0);
+        mMultiSimSettingControllerUT.notifySubscriptionInfoChanged();
+        sendCarrierConfigChanged(0/*phoneid*/, 3/*subid*/);
+        processAllMessages();
+
+        // Sub 3 should be default sub.
+        verify(mSubscriptionManagerService).setDefaultDataSubId(3);
+        verify(mSubscriptionManagerService).setDefaultVoiceSubId(3);
+        verify(mSubscriptionManagerService).setDefaultSmsSubId(3);
+        verify(mContext, never()).sendBroadcast(any());
+    }
+
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
index 6743d1c..0e04aff 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneConfigurationManagerTest.java
@@ -57,6 +57,7 @@
 import org.mockito.Mockito;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -157,6 +158,7 @@
         init(1);
         assertEquals(PhoneCapability.DEFAULT_SSSS_CAPABILITY, mPcm.getStaticPhoneCapability());
 
+        mPcm.updateRadioCapability();
         setAndVerifyStaticCapability(PhoneCapability.DEFAULT_DSDS_CAPABILITY);
     }
 
@@ -171,6 +173,7 @@
                 .setMaxActiveVoiceSubscriptions(2)
                 .build();
 
+        mPcm.updateRadioCapability();
         ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
         verify(mMockRadioConfig).getPhoneCapability(captor.capture());
         Message msg = captor.getValue();
@@ -188,7 +191,7 @@
         init(2);
         mPcm.updateSimultaneousCallingSupport();
 
-        int[] enabledLogicalSlots = {0, 1};
+        List<Integer> enabledLogicalSlots = Arrays.asList(0, 1);
         ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
         verify(mMockRadioConfig).updateSimultaneousCallingSupport(captor.capture());
         Message msg = captor.getValue();
@@ -209,7 +212,7 @@
         mPcm.updateSimultaneousCallingSupport();
 
         // Have the modem send invalid phone slots -1 and 5:
-        int[] invalidEnabledLogicalSlots = {-1, 5};
+        List<Integer> invalidEnabledLogicalSlots = Arrays.asList(-1, 5);
         ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
         verify(mMockRadioConfig).updateSimultaneousCallingSupport(captor.capture());
         Message msg = captor.getValue();
@@ -245,14 +248,14 @@
         mPcm.registerForSimultaneousCellularCallingSlotsChanged(newSlots ->
                 cachedSimultaneousCallingSlots[0] = newSlots);
 
-        mPcm.getStaticPhoneCapability();
+        mPcm.updateRadioCapability();
         setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
         ArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener> cBCaptor =
                 ArgumentCaptor.forClass(SubscriptionManager.OnSubscriptionsChangedListener.class);
         verify(mMockRegistryManager).addOnSubscriptionsChangedListener(cBCaptor.capture(), any());
         processAllMessages();
 
-        int[] enabledLogicalSlots = {0, 1};
+        List<Integer> enabledLogicalSlots = Arrays.asList(0, 1);
         HashSet<Integer> expectedSlots = new HashSet<>(2);
         for (int i : enabledLogicalSlots) {
             expectedSlots.add(i);
@@ -296,7 +299,7 @@
 
         // Simultaneous calling enabled
         mPcm.updateSimultaneousCallingSupport();
-        int[] enabledLogicalSlots = {0, 1};
+        List<Integer> enabledLogicalSlots = Arrays.asList(0, 1);
         ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
         verify(mMockRadioConfig).updateSimultaneousCallingSupport(captor.capture());
         Message msg = captor.getValue();
@@ -318,7 +321,7 @@
 
         // Simultaneous Calling Disabled
         mPcm.updateSimultaneousCallingSupport();
-        int[] disabled = {};
+        List<Integer> disabled = List.of();
         captor = ArgumentCaptor.forClass(Message.class);
         verify(mMockRadioConfig, times(2)).updateSimultaneousCallingSupport(captor.capture());
         msg = captor.getAllValues().get(1);
@@ -346,13 +349,13 @@
 
         // Set the capability to DSDA mode to register listener, which will also trigger
         // simultaneous calling evaluation
-        mPcm.getCurrentPhoneCapability();
+        mPcm.updateRadioCapability();
         setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
         ArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener> cBCaptor =
                 ArgumentCaptor.forClass(SubscriptionManager.OnSubscriptionsChangedListener.class);
         verify(mMockRegistryManager).addOnSubscriptionsChangedListener(cBCaptor.capture(), any());
 
-        int[] enabledLogicalSlots = {0, 1};
+        List<Integer> enabledLogicalSlots = Arrays.asList(0, 1);
         ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
         verify(mMockRadioConfig).updateSimultaneousCallingSupport(captor.capture());
         Message msg = captor.getValue();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
index 1c4e43d..bf9ced3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneNumberUtilsTest.java
@@ -21,7 +21,10 @@
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
+import com.android.internal.telephony.flags.Flags;
+
 import android.net.Uri;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.telephony.PhoneNumberUtils;
 import android.text.SpannableStringBuilder;
 import android.text.style.TtsSpan;
@@ -32,6 +35,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 
 public class PhoneNumberUtilsTest {
@@ -40,6 +44,8 @@
 
     private int mOldMinMatch;
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     public void setUp() throws Exception {
         mOldMinMatch = PhoneNumberUtils.getMinMatchForTest();
@@ -613,6 +619,60 @@
         assertEquals("+1 650-555-1212", PhoneNumberUtils.formatNumber("+16505551212", "jp"));
     }
 
+    /**
+     * Test to ensure that when international calls to Singapore are being placed the country
+     * code is present with and without the feature flag enabled.
+     */
+    @SmallTest
+    @Test
+    public void testFormatSingaporeInternational() {
+        // Disable feature flag.
+        mSetFlagsRule.disableFlags(Flags.FLAG_REMOVE_COUNTRY_CODE_FROM_LOCAL_SINGAPORE_CALLS);
+
+        // International call from a US iso
+        assertEquals("+65 6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "US"));
+
+        // Lowercase country iso
+        assertEquals("+65 6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "us"));
+
+        // Enable feature flag
+        mSetFlagsRule.enableFlags(Flags.FLAG_REMOVE_COUNTRY_CODE_FROM_LOCAL_SINGAPORE_CALLS);
+
+        // Internal call from a US iso
+        assertEquals("+65 6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "US"));
+
+        // Lowercase country iso
+        assertEquals("+65 6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "us"));
+        mSetFlagsRule.disableFlags(Flags.FLAG_REMOVE_COUNTRY_CODE_FROM_LOCAL_SINGAPORE_CALLS);
+    }
+
+    /**
+     * Test to ensure that when local calls from Singaporean numbers are being placed to other
+     * Singaporean numbers the country code +65 is not being shown.
+     */
+    @SmallTest
+    @Test
+    public void testFormatSingaporeNational() {
+        // Disable feature flag.
+        mSetFlagsRule.disableFlags(Flags.FLAG_REMOVE_COUNTRY_CODE_FROM_LOCAL_SINGAPORE_CALLS);
+
+        // Local call from a Singaporean number to a Singaporean number
+        assertEquals("+65 6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "SG"));
+
+        // Lowercase country iso.
+        assertEquals("+65 6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "sg"));
+
+        // Enable feature flag.
+        mSetFlagsRule.enableFlags(Flags.FLAG_REMOVE_COUNTRY_CODE_FROM_LOCAL_SINGAPORE_CALLS);
+
+        // Local call from a Singaporean number to a Singaporean number.
+        assertEquals("6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "SG"));
+
+        // Lowercase country iso.
+        assertEquals("6521 8000", PhoneNumberUtils.formatNumber("+6565218000", "sg"));
+        mSetFlagsRule.disableFlags(Flags.FLAG_REMOVE_COUNTRY_CODE_FROM_LOCAL_SINGAPORE_CALLS);
+    }
+
     @SmallTest
     @Test
     public void testFormatNumber_LeadingStarAndHash() {
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
index c2af9d8..d8005e8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
@@ -43,7 +43,8 @@
 import android.os.Build;
 import android.os.RemoteException;
 import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.uicc.IsimUiccRecords;
 import com.android.internal.telephony.uicc.SIMRecords;
diff --git a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
index bfe9649..88c5389 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/RILTest.java
@@ -330,7 +330,7 @@
         proxies.put(HAL_SERVICE_MODEM, mRadioModemProxy);
         mRILInstance = new RIL(context,
                 RadioAccessFamily.getRafFromNetworkType(RILConstants.PREFERRED_NETWORK_MODE),
-                Phone.PREFERRED_CDMA_SUBSCRIPTION, 0, proxies);
+                Phone.PREFERRED_CDMA_SUBSCRIPTION, 0, proxies, mFeatureFlags);
         mRILUnderTest = spy(mRILInstance);
         doReturn(mRadioProxy).when(mRILUnderTest).getRadioProxy();
         doReturn(mDataProxy).when(mRILUnderTest).getRadioServiceProxy(eq(RadioDataProxy.class));
@@ -354,6 +354,8 @@
             replaceInstance(RIL.class, "mHalVersion", mRILUnderTest, mHalVersionV14);
         } catch (Exception e) {
         }
+
+        doReturn(true).when(mFeatureFlags).combineRilDeathHandle();
     }
 
     @After
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index 121136d..e3da458 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -2605,10 +2605,15 @@
         waitForLastHandlerAction(mSSTTestHandler.getThreadHandler());
 
         // PS WLAN
+        int wlanRat = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        if (wlanState == NetworkRegistrationInfo.REGISTRATION_STATE_HOME
+                || wlanState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING) {
+            wlanRat = TelephonyManager.NETWORK_TYPE_IWLAN;
+        }
         NetworkRegistrationInfo dataIwlanResult = new NetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
-                wlanState, TelephonyManager.NETWORK_TYPE_IWLAN, 0, false,
-                null, null, "", 1, false, false, false, lteVopsSupportInfo);
+                wlanState, wlanRat, 0, false, null, null,
+                "", 1, false, false, false, lteVopsSupportInfo);
         sst.sendMessage(sst.obtainMessage(
                 ServiceStateTracker.EVENT_POLL_STATE_PS_IWLAN_REGISTRATION,
                 new AsyncResult(sst.mPollingContext, dataIwlanResult, null)));
@@ -2645,6 +2650,47 @@
         verify(mLocaleTracker).updateOperatorNumeric(eq(""));
     }
 
+    /**
+     * Ensure that LocaleTracker is not updated with mcc when only IWLAN is not in-service and the
+     * ims registration status is connected over iwlan,
+     */
+    @Test
+    public void testLocaleTrackerUpdateWithImsRegistrationTechIwlan() {
+        // Start state: Cell data only LTE + IWLAN
+        final String[] OpNamesResult = new String[] { "carrier long", "carrier", "310310" };
+        // Clear invocations for mLocaleTracker as precondition before test case execution & as part
+        // test setup
+        Mockito.clearInvocations(mLocaleTracker);
+
+        // Both Cellular abd Iwlan is in-service.
+        changeRegStateWithIwlanOperatorNumeric(
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
+                TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, OpNamesResult, true);
+        verify(mLocaleTracker).updateOperatorNumeric(eq(OpNamesResult[2]));
+
+        // Test with Cellular as NOT_REG
+        changeRegStateWithIwlanOperatorNumeric(
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING,
+                TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, OpNamesResult, true);
+        /* cellId based mccmnc */
+        verify(mLocaleTracker).updateOperatorNumeric(eq("00101"));
+
+        // IMS over Iwlan is registered.
+        doReturn(mImsPhone)
+                .when(mPhone).getImsPhone();
+        doReturn(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN)
+                .when(mImsPhone).getImsRegistrationTech();
+
+        // Test with Iwlan as NOT_REG
+        changeRegStateWithIwlanOperatorNumeric(
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING,
+                TelephonyManager.NETWORK_TYPE_UNKNOWN,
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING,
+                OpNamesResult, false);
+        verify(mLocaleTracker).updateOperatorNumeric(eq(""));
+    }
     @Test
     public void testGetServiceProviderNameWithBrandOverride() {
         String brandOverride = "spn from brand override";
@@ -3425,9 +3471,12 @@
                         AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
         assertEquals(2, nriList.size());
         for (NetworkRegistrationInfo nri : nriList) {
-            assertTrue(Arrays.equals(satelliteSupportedServices, nri.getAvailableServices().stream()
-                    .mapToInt(Integer::intValue)
-                    .toArray()));
+            if (nri.isInService()) {
+                assertTrue(Arrays.equals(satelliteSupportedServices,
+                        nri.getAvailableServices().stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+            }
         }
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java
index d3fde34..879b184 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SimultaneousCallingTrackerTest.java
@@ -172,7 +172,7 @@
     }
 
     private void setAndVerifyStaticCapability(PhoneCapability capability) {
-        mPcm.getCurrentPhoneCapability();
+        mPcm.updateRadioCapability();
         ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
         verify(mMockRadioConfig).getPhoneCapability(captor.capture());
         Message msg = captor.getValue();
@@ -185,7 +185,8 @@
 
     }
 
-    private void setAndVerifySlotsSupportingSimultaneousCellularCalling(int[] enabledLogicalSlots) {
+    private void setAndVerifySlotsSupportingSimultaneousCellularCalling(
+            List<Integer> enabledLogicalSlots) {
         ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
         verify(mMockRadioConfig).updateSimultaneousCallingSupport(captor.capture());
         Message msg = captor.getValue();
@@ -241,7 +242,7 @@
         init(2);
         setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
 
-        int[] enabledLogicalSlots = {0, 1};
+        List<Integer> enabledLogicalSlots = Arrays.asList(0, 1);
         setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
 
         // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
@@ -264,7 +265,7 @@
         setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
 
         // Have the modem inform telephony that only phone slot 0 supports DSDA:
-        int[] enabledLogicalSlots = {0};
+        List<Integer> enabledLogicalSlots = List.of(0);
         setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
 
         // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
@@ -286,7 +287,7 @@
         init(2);
         setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
 
-        int[] enabledLogicalSlots = {0, 1};
+        List<Integer> enabledLogicalSlots = Arrays.asList(0, 1);
         setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
 
         // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
@@ -309,7 +310,7 @@
         init(2);
         setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
 
-        int[] enabledLogicalSlots = {0, 1};
+        List<Integer> enabledLogicalSlots = Arrays.asList(0, 1);
         setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
 
         // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
@@ -356,7 +357,7 @@
         init(2);
         setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
 
-        int[] enabledLogicalSlots = {0, 1};
+        List<Integer> enabledLogicalSlots = Arrays.asList(0, 1);
         setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
 
         // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
@@ -384,7 +385,7 @@
         init(2);
         setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
 
-        int[] enabledLogicalSlots = {0};
+        List<Integer> enabledLogicalSlots = List.of(0);
         setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
 
         // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
@@ -416,7 +417,7 @@
         init(2);
         setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
 
-        int[] enabledLogicalSlots = {0, 1};
+        List<Integer> enabledLogicalSlots = Arrays.asList(0, 1);
         setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
 
         // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
@@ -449,7 +450,7 @@
         init(3);
         setAndVerifyStaticCapability(STATIC_DSDA_CAPABILITY);
 
-        int[] enabledLogicalSlots = {0, 1};
+        List<Integer> enabledLogicalSlots = Arrays.asList(0, 1);
         setAndVerifySlotsSupportingSimultaneousCellularCalling(enabledLogicalSlots);
 
         // Trigger onSubscriptionsChanged by updating the subscription ID of a phone slot:
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
index 70e3d6b..414fb3b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsDispatchersControllerTest.java
@@ -111,8 +111,8 @@
         }
 
         public void testNotifySmsSentToEmergencyStateTracker(String destAddr, long messageId,
-                boolean isOverIms) {
-            notifySmsSentToEmergencyStateTracker(destAddr, messageId, isOverIms);
+                boolean isOverIms, boolean isLastSmsPart) {
+            notifySmsSentToEmergencyStateTracker(destAddr, messageId, isOverIms, isLastSmsPart);
         }
 
         public void testNotifySmsSentFailedToEmergencyStateTracker(String destAddr,
@@ -456,7 +456,7 @@
 
     @Test
     @SmallTest
-    public void testSendEmergencyTextWhenDomainPs() throws Exception {
+    public void testSendTextForEmergencyWhenDomainPs() throws Exception {
         setUpDomainSelectionConnection();
         setUpSmsDispatchers();
         setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
@@ -488,7 +488,7 @@
 
     @Test
     @SmallTest
-    public void testSendEmergencyTextWhenEmergencyStateTrackerReturnsFailure() throws Exception {
+    public void testSendTextForEmergencyWhenEmergencyStateTrackerReturnsFailure() throws Exception {
         setUpDomainSelectionConnection();
         setUpSmsDispatchers();
         setUpEmergencyStateTracker(DisconnectCause.OUT_OF_SERVICE);
@@ -503,16 +503,88 @@
 
     @Test
     @SmallTest
+    public void testSendMultipartTextForEmergencyWhenDomainPs() throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        ArrayList<String> parts = new ArrayList<>();
+        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);
+        processAllMessages();
+
+        SmsDispatchersController.DomainSelectionConnectionHolder holder =
+                mSmsDispatchersController.testGetDomainSelectionConnectionHolder(true);
+        verify(mEmergencySmsDsc).requestDomainSelection(any(), any());
+        assertNotNull(holder);
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isEmergency());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(1, holder.getPendingRequests().size());
+
+        mDscFuture.complete(NetworkRegistrationInfo.DOMAIN_PS);
+        processAllMessages();
+
+        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));
+        assertNull(holder.getConnection());
+        assertFalse(holder.isDomainSelectionRequested());
+        assertEquals(0, holder.getPendingRequests().size());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendRetrySmsForEmergencyWhenDomainPs() throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        when(mPhone.getPhoneType()).thenReturn(PhoneConstants.PHONE_TYPE_GSM);
+        when(mImsSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP);
+        when(mCdmaSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP2);
+        when(mGsmSmsDispatcher.getFormat()).thenReturn(SmsConstants.FORMAT_3GPP);
+        replaceInstance(SMSDispatcher.SmsTracker.class,
+                "mFormat", mTracker, SmsConstants.FORMAT_3GPP);
+        replaceInstance(SMSDispatcher.SmsTracker.class, "mDestAddress", mTracker, "911");
+
+        mSmsDispatchersController.sendRetrySms(mTracker);
+        processAllMessages();
+
+        SmsDispatchersController.DomainSelectionConnectionHolder holder =
+                mSmsDispatchersController.testGetDomainSelectionConnectionHolder(true);
+        verify(mEmergencySmsDsc).requestDomainSelection(any(), any());
+        assertNotNull(holder);
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isEmergency());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(1, holder.getPendingRequests().size());
+
+        mDscFuture.complete(NetworkRegistrationInfo.DOMAIN_PS);
+        processAllMessages();
+
+        verify(mEmergencySmsDsc).finishSelection();
+        verify(mImsSmsDispatcher).sendSms(eq(mTracker));
+        assertNull(holder.getConnection());
+        assertFalse(holder.isDomainSelectionRequested());
+        assertEquals(0, holder.getPendingRequests().size());
+    }
+
+    @Test
+    @SmallTest
     public void testNotifySmsSentToEmergencyStateTrackerOnDomainCs() throws Exception {
         setUpDomainSelectionEnabled(true);
         setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
 
-        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L, false);
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L, false, true);
         processAllMessages();
 
         verify(mTelephonyManager).isEmergencyNumber(eq("911"));
         verify(mEmergencyStateTracker)
-                .endSms(eq("1"), eq(true), eq(NetworkRegistrationInfo.DOMAIN_CS));
+                .endSms(eq("1"), eq(true), eq(NetworkRegistrationInfo.DOMAIN_CS), eq(true));
     }
 
     @Test
@@ -521,12 +593,12 @@
         setUpDomainSelectionEnabled(true);
         setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
 
-        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L, true);
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L, true, true);
         processAllMessages();
 
         verify(mTelephonyManager).isEmergencyNumber(eq("911"));
         verify(mEmergencyStateTracker)
-                .endSms(eq("1"), eq(true), eq(NetworkRegistrationInfo.DOMAIN_PS));
+                .endSms(eq("1"), eq(true), eq(NetworkRegistrationInfo.DOMAIN_PS), eq(true));
     }
 
     @Test
@@ -535,11 +607,12 @@
         setUpDomainSelectionEnabled(true);
         setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
 
-        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("1234", 1L, true);
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("1234", 1L, true, true);
         processAllMessages();
 
         verify(mTelephonyManager).isEmergencyNumber(eq("1234"));
-        verify(mEmergencyStateTracker, never()).endSms(anyString(), anyBoolean(), anyInt());
+        verify(mEmergencyStateTracker, never())
+                .endSms(anyString(), anyBoolean(), anyInt(), anyBoolean());
     }
 
     @Test
@@ -547,7 +620,7 @@
     public void testNotifySmsSentToEmergencyStateTrackerWithoutEmergencyStateTracker()
             throws Exception {
         setUpDomainSelectionEnabled(true);
-        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L, true);
+        mSmsDispatchersController.testNotifySmsSentToEmergencyStateTracker("911", 1L, true, true);
 
         verify(mTelephonyManager, never()).isEmergencyNumber(anyString());
     }
@@ -563,7 +636,7 @@
 
         verify(mTelephonyManager).isEmergencyNumber(eq("911"));
         verify(mEmergencyStateTracker)
-                .endSms(eq("1"), eq(false), eq(NetworkRegistrationInfo.DOMAIN_CS));
+                .endSms(eq("1"), eq(false), eq(NetworkRegistrationInfo.DOMAIN_CS), eq(true));
     }
 
     @Test
@@ -577,7 +650,7 @@
 
         verify(mTelephonyManager).isEmergencyNumber(eq("911"));
         verify(mEmergencyStateTracker)
-                .endSms(eq("1"), eq(false), eq(NetworkRegistrationInfo.DOMAIN_PS));
+                .endSms(eq("1"), eq(false), eq(NetworkRegistrationInfo.DOMAIN_PS), eq(true));
     }
 
     @Test
@@ -591,7 +664,8 @@
         processAllMessages();
 
         verify(mTelephonyManager).isEmergencyNumber(eq("1234"));
-        verify(mEmergencyStateTracker, never()).endSms(anyString(), anyBoolean(), anyInt());
+        verify(mEmergencyStateTracker, never())
+                .endSms(anyString(), anyBoolean(), anyInt(), anyBoolean());
     }
 
     @Test
@@ -742,15 +816,237 @@
         setUpDomainSelectionConnection();
         setUpSmsDispatchers();
         when(mFeatureFlags.smsDomainSelectionEnabled()).thenReturn(false);
+        when(mImsSmsDispatcher.isAvailable()).thenReturn(true);
+
+        mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
+                "test-app", 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));
+    }
+
+    @Test
+    @SmallTest
+    public void testSendTextWhenDomainSelectionFinishedAndNewTextSent() throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
 
         mSmsDispatchersController.sendText("1111", "2222", "text", mSentIntent, null, null,
                 "test-app", false, 0, false, 10, false, 1L, false);
         processAllMessages();
 
-        // Expect that the domain selection logic will not be executed.
         SmsDispatchersController.DomainSelectionConnectionHolder holder =
                 mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
-        assertNull(holder);
+        verify(mSmsDsc).requestDomainSelection(any(), any());
+        assertNotNull(holder);
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(1, holder.getPendingRequests().size());
+
+        // Expect that finishDomainSelection is called while a new pending request is posted.
+        mDscFuture.complete(NetworkRegistrationInfo.DOMAIN_PS);
+
+        SmsDomainSelectionConnection newSmsDsc = Mockito.mock(SmsDomainSelectionConnection.class);
+        mSmsDispatchersController.setDomainSelectionResolverProxy(
+                new SmsDispatchersController.DomainSelectionResolverProxy() {
+                    @Override
+                    @Nullable
+                    public DomainSelectionConnection getDomainSelectionConnection(Phone phone,
+                            @DomainSelectionService.SelectorType int selectorType,
+                            boolean isEmergency) {
+                        return newSmsDsc;
+                    }
+
+                    @Override
+                    public boolean isDomainSelectionSupported() {
+                        return true;
+                    }
+                });
+        CompletableFuture newDscFuture = new CompletableFuture<>();
+        when(newSmsDsc.requestDomainSelection(
+                any(DomainSelectionService.SelectionAttributes.class),
+                any(DomainSelectionConnection.DomainSelectionConnectionCallback.class)))
+                .thenReturn(newDscFuture);
+
+        // 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);
+        processAllMessages();
+
+        verify(mSmsDsc).finishSelection();
+
+        verify(newSmsDsc).requestDomainSelection(any(), any());
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(1, holder.getPendingRequests().size());
+
+        newDscFuture.complete(NetworkRegistrationInfo.DOMAIN_PS);
+        processAllMessages();
+
+        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));
+        assertNull(holder.getConnection());
+        assertFalse(holder.isDomainSelectionRequested());
+        assertEquals(0, holder.getPendingRequests().size());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendTextForEmergencyWhenDomainSelectionFinishedAndNewTextSent()
+            throws Exception {
+        setUpDomainSelectionConnection();
+        setUpSmsDispatchers();
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+
+        mSmsDispatchersController.sendText("911", "2222", "text", mSentIntent, null, null,
+                "test-app", false, 0, false, 10, false, 1L, false);
+        processAllMessages();
+
+        SmsDispatchersController.DomainSelectionConnectionHolder holder =
+                mSmsDispatchersController.testGetDomainSelectionConnectionHolder(true);
+        verify(mEmergencySmsDsc).requestDomainSelection(any(), any());
+        assertNotNull(holder);
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isEmergency());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(1, holder.getPendingRequests().size());
+
+        // Expect that finishDomainSelection is called while a new pending request is posted.
+        mDscFuture.complete(NetworkRegistrationInfo.DOMAIN_PS);
+
+        EmergencySmsDomainSelectionConnection newEmergencySmsDsc =
+                Mockito.mock(EmergencySmsDomainSelectionConnection.class);
+        mSmsDispatchersController.setDomainSelectionResolverProxy(
+                new SmsDispatchersController.DomainSelectionResolverProxy() {
+                    @Override
+                    @Nullable
+                    public DomainSelectionConnection getDomainSelectionConnection(Phone phone,
+                            @DomainSelectionService.SelectorType int selectorType,
+                            boolean isEmergency) {
+                        return newEmergencySmsDsc;
+                    }
+
+                    @Override
+                    public boolean isDomainSelectionSupported() {
+                        return true;
+                    }
+                });
+        CompletableFuture newDscFuture = new CompletableFuture<>();
+        when(newEmergencySmsDsc.requestDomainSelection(
+                any(DomainSelectionService.SelectionAttributes.class),
+                any(DomainSelectionConnection.DomainSelectionConnectionCallback.class)))
+                .thenReturn(newDscFuture);
+
+        // 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);
+        processAllMessages();
+
+        verify(mEmergencySmsDsc).finishSelection();
+
+        verify(newEmergencySmsDsc).requestDomainSelection(any(), any());
+        assertNotNull(holder.getConnection());
+        assertTrue(holder.isDomainSelectionRequested());
+        assertEquals(1, holder.getPendingRequests().size());
+
+        newDscFuture.complete(NetworkRegistrationInfo.DOMAIN_PS);
+        processAllMessages();
+
+        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));
+        assertNull(holder.getConnection());
+        assertFalse(holder.isDomainSelectionRequested());
+        assertEquals(0, holder.getPendingRequests().size());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendTextFallbackWhenDomainSelectionConnectionNotCreated() throws Exception {
+        mSmsDispatchersController.setDomainSelectionResolverProxy(
+                new SmsDispatchersController.DomainSelectionResolverProxy() {
+                    @Override
+                    @Nullable
+                    public DomainSelectionConnection getDomainSelectionConnection(Phone phone,
+                            @DomainSelectionService.SelectorType int selectorType,
+                            boolean isEmergency) {
+                        return null;
+                    }
+
+                    @Override
+                    public boolean isDomainSelectionSupported() {
+                        return true;
+                    }
+                });
+        when(mFeatureFlags.smsDomainSelectionEnabled()).thenReturn(true);
+        setUpSmsDispatchers();
+        when(mImsSmsDispatcher.isAvailable()).thenReturn(true);
+
+        // 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);
+        processAllMessages();
+
+        SmsDispatchersController.DomainSelectionConnectionHolder holder =
+                mSmsDispatchersController.testGetDomainSelectionConnectionHolder(false);
+        assertNotNull(holder);
+        assertNull(holder.getConnection());
+        assertFalse(holder.isDomainSelectionRequested());
+        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));
+    }
+
+    @Test
+    @SmallTest
+    public void testSendTextFallbackForEmergencyWhenDomainSelectionConnectionNotCreated()
+            throws Exception {
+        mSmsDispatchersController.setDomainSelectionResolverProxy(
+                new SmsDispatchersController.DomainSelectionResolverProxy() {
+                    @Override
+                    @Nullable
+                    public DomainSelectionConnection getDomainSelectionConnection(Phone phone,
+                            @DomainSelectionService.SelectorType int selectorType,
+                            boolean isEmergency) {
+                        return null;
+                    }
+
+                    @Override
+                    public boolean isDomainSelectionSupported() {
+                        return true;
+                    }
+                });
+        when(mFeatureFlags.smsDomainSelectionEnabled()).thenReturn(true);
+        setUpSmsDispatchers();
+        setUpEmergencyStateTracker(DisconnectCause.NOT_DISCONNECTED);
+        when(mImsSmsDispatcher.isAvailable()).thenReturn(true);
+
+        // 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);
+        processAllMessages();
+
+        SmsDispatchersController.DomainSelectionConnectionHolder holder =
+                mSmsDispatchersController.testGetDomainSelectionConnectionHolder(true);
+        assertNotNull(holder);
+        assertNull(holder.getConnection());
+        assertTrue(holder.isEmergency());
+        assertFalse(holder.isDomainSelectionRequested());
+        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));
     }
 
     private void switchImsSmsFormat(int phoneType) {
@@ -843,7 +1139,8 @@
                 mSmsDispatchersController, mEmergencyStateTracker);
         when(mEmergencyStateTracker.startEmergencySms(any(Phone.class), anyString(), anyBoolean()))
                 .thenReturn(mEmergencySmsFuture);
-        doNothing().when(mEmergencyStateTracker).endSms(anyString(), anyBoolean(), anyInt());
+        doNothing().when(mEmergencyStateTracker)
+                .endSms(anyString(), anyBoolean(), anyInt(), anyBoolean());
         mEmergencySmsFuture.complete(result);
         when(mTelephonyManager.isEmergencyNumber(eq("911"))).thenReturn(true);
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
index 6369825..d4717dd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
@@ -73,7 +73,6 @@
 
     // Mocked classes
     private Context mMockContext;
-    private FeatureFlags mMockFeatureFlag;
     private AppOpsManager mMockAppOps;
     private SubscriptionManager mMockSubscriptionManager;
     private ITelephony mMockTelephony;
@@ -92,7 +91,6 @@
     @Before
     public void setUp() throws Exception {
         mMockContext = mock(Context.class);
-        mMockFeatureFlag = mock(FeatureFlags.class);
         mMockAppOps = mock(AppOpsManager.class);
         mMockSubscriptionManager = mock(SubscriptionManager.class);
         mMockTelephony = mock(ITelephony.class);
@@ -135,13 +133,11 @@
         when(mMockContext.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
                 PID, UID)).thenReturn(PackageManager.PERMISSION_DENIED);
 
-        replaceFeatureFlag(mMockFeatureFlag);
         setTelephonyMockAsService();
     }
 
     @After
     public void tearDown() throws Exception {
-        replaceFeatureFlag(mRealFeatureFlagToBeRestored);
         mMockContentResolver = null;
         mFakeSettingsConfigProvider = null;
         mRealFeatureFlagToBeRestored = null;
@@ -554,9 +550,7 @@
     }
 
     @Test
-    public void testCheckSubscriptionAssociatedWithUser_badSub_flag_enabled() {
-        doReturn(true).when(mMockFeatureFlag).rejectBadSubIdInteraction();
-
+    public void testCheckSubscriptionAssociatedWithUser() {
         doThrow(new IllegalArgumentException("has no records on device"))
                 .when(mMockSubscriptionManager).isSubscriptionAssociatedWithUser(SUB_ID,
                         UserHandle.SYSTEM);
@@ -564,19 +558,6 @@
                 UserHandle.SYSTEM));
     }
 
-    @Test
-    public void testCheckSubscriptionAssociatedWithUser_badSub_flag_disabled() {
-        doReturn(false).when(mMockFeatureFlag).rejectBadSubIdInteraction();
-
-        doThrow(new IllegalArgumentException("No records found for sub"))
-                .when(mMockSubscriptionManager).isSubscriptionAssociatedWithUser(SUB_ID,
-                        UserHandle.SYSTEM);
-        assertTrue(TelephonyPermissions.checkSubscriptionAssociatedWithUser(mMockContext, SUB_ID,
-                UserHandle.SYSTEM));
-        assertTrue(TelephonyPermissions.checkSubscriptionAssociatedWithUser(mMockContext,
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID, UserHandle.SYSTEM));
-    }
-
     // Put mMockTelephony into service cache so that TELEPHONY_SUPPLIER will get it.
     private void setTelephonyMockAsService() throws Exception {
         when(mMockTelephonyBinder.queryLocalInterface(anyString())).thenReturn(mMockTelephony);
@@ -666,13 +647,4 @@
         field.setAccessible(true);
         field.set(providerHolder, iContentProvider);
     }
-
-    private synchronized void replaceFeatureFlag(final FeatureFlags newValue)
-            throws Exception {
-        Field field = TelephonyPermissions.class.getDeclaredField("sFeatureFlag");
-        field.setAccessible(true);
-
-        mRealFeatureFlagToBeRestored = (FeatureFlags) field.get(null);
-        field.set(null, newValue);
-    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
index 6050b18..3e447a9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java
@@ -117,6 +117,7 @@
     private CellIdentity mCellIdentityForRegiFail;
     private int mRegistrationFailReason;
     private Set<Integer> mSimultaneousCallingSubscriptions;
+    private boolean mCarrierRoamingNtnMode;
 
     // All events contribute to TelephonyRegistry#isPhoneStatePermissionRequired
     private static final Set<Integer> READ_PHONE_STATE_EVENTS;
@@ -192,7 +193,8 @@
             TelephonyCallback.BarringInfoListener,
             TelephonyCallback.RegistrationFailedListener,
             TelephonyCallback.DataActivityListener,
-            TelephonyCallback.SimultaneousCellularCallingSupportListener {
+            TelephonyCallback.SimultaneousCellularCallingSupportListener,
+            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.
         public AtomicInteger invocationCount = new AtomicInteger(0);
@@ -287,6 +289,12 @@
             invocationCount.incrementAndGet();
             mSimultaneousCallingSubscriptions = simultaneousCallingSubscriptionIds;
         }
+
+        @Override
+        public void onCarrierRoamingNtnModeChanged(boolean active) {
+            invocationCount.incrementAndGet();
+            mCarrierRoamingNtnMode = active;
+        }
     }
 
     private void addTelephonyRegistryService() {
@@ -1562,4 +1570,19 @@
         processAllMessages();
         assertEquals(subIdSet, mSimultaneousCallingSubscriptions);
     }
+
+    @Test
+    public void testNotifyCarrierRoamingNtnModeChanged() {
+        int subId = INVALID_SUBSCRIPTION_ID;
+        doReturn(mMockSubInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(anyInt());
+        doReturn(0/*slotIndex*/).when(mMockSubInfo).getSimSlotIndex();
+        int[] events = {TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED};
+
+        mTelephonyRegistry.listenWithEventList(false, false, subId, mContext.getOpPackageName(),
+                mContext.getAttributionTag(), mTelephonyCallback.callback, events, true);
+
+        mTelephonyRegistry.notifyCarrierRoamingNtnModeChanged(subId, true);
+        processAllMessages();
+        assertTrue(mCarrierRoamingNtnMode);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
index 673acbc..38b4f77 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyTest.java
@@ -35,6 +35,7 @@
 import android.app.IActivityManager;
 import android.app.KeyguardManager;
 import android.app.PropertyInvalidatedCache;
+import android.app.admin.DevicePolicyManager;
 import android.app.usage.NetworkStatsManager;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
@@ -304,6 +305,7 @@
     protected AppOpsManager mAppOpsManager;
     protected CarrierConfigManager mCarrierConfigManager;
     protected UserManager mUserManager;
+    protected DevicePolicyManager mDevicePolicyManager;
     protected KeyguardManager mKeyguardManager;
     protected VcnManager mVcnManager;
     protected NetworkPolicyManager mNetworkPolicyManager;
@@ -630,6 +632,8 @@
         mCarrierConfigManager =
                 (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
+                Context.DEVICE_POLICY_SERVICE);
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mVcnManager = mContext.getSystemService(VcnManager.class);
         mNetworkPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
@@ -685,7 +689,7 @@
         doReturn(mEriManager).when(mTelephonyComponentFactory)
                 .makeEriManager(nullable(Phone.class), anyInt());
         doReturn(mLinkBandwidthEstimator).when(mTelephonyComponentFactory)
-                .makeLinkBandwidthEstimator(nullable(Phone.class));
+                .makeLinkBandwidthEstimator(nullable(Phone.class), any(Looper.class));
         doReturn(mDataProfileManager).when(mTelephonyComponentFactory)
                 .makeDataProfileManager(any(Phone.class), any(DataNetworkController.class),
                         any(DataServiceManager.class), any(Looper.class),
diff --git a/tests/telephonytests/src/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiverTest.java b/tests/telephonytests/src/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiverTest.java
index 629327d..0563481 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiverTest.java
@@ -16,34 +16,47 @@
 
 package com.android.internal.telephony.configupdate;
 
-import static com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver.UPDATE_CONTENT_PATH;
+import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE;
+import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE;
+
+import static com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver.NEW_CONFIG_CONTENT_PATH;
 import static com.android.internal.telephony.configupdate.TelephonyConfigUpdateInstallReceiver.UPDATE_DIR;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.atLeast;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.content.Intent;
+import android.util.ArraySet;
 
 import androidx.annotation.Nullable;
 
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.satellite.SatelliteConfig;
 import com.android.internal.telephony.satellite.SatelliteConfigParser;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.io.File;
 import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -74,7 +87,7 @@
         TelephonyConfigUpdateInstallReceiver testReceiver =
                 new TelephonyConfigUpdateInstallReceiver();
         assertEquals(UPDATE_DIR, testReceiver.getUpdateDir().toString());
-        assertEquals(new File(new File(UPDATE_DIR), UPDATE_CONTENT_PATH).toString(),
+        assertEquals(new File(new File(UPDATE_DIR), NEW_CONFIG_CONTENT_PATH).toString(),
                 testReceiver.getUpdateContent().toString());
     }
 
@@ -92,38 +105,133 @@
         // create spyTelephonyConfigUpdateInstallReceiver
         TelephonyConfigUpdateInstallReceiver spyTelephonyConfigUpdateInstallReceiver =
                 spy(new TelephonyConfigUpdateInstallReceiver());
-
-        // mock BeforeParser
-        String mBase64StrForPBByteArray =
-                "CjYIBBIeCAESDgoGMzEwMTYwEAEQAhADEgoKBjMxMDIyMBADGhIKCjAxMjM0NTY3ODkSAlVTGAE=";
-        byte[] mBytesProtoBuffer = Base64.getDecoder().decode(mBase64StrForPBByteArray);
-        doReturn(mBytesProtoBuffer).when(
-                spyTelephonyConfigUpdateInstallReceiver).getCurrentContent();
-        SatelliteConfigParser mMockSatelliteConfigParserBefore =
-                spy(new SatelliteConfigParser(mBytesProtoBuffer));
-        doReturn(mMockSatelliteConfigParserBefore).when(
-                spyTelephonyConfigUpdateInstallReceiver).getConfigParser(DOMAIN_SATELLITE);
-
-        // mock UpdatedParser
-        SatelliteConfigParser spySatelliteConfigParserAfter =
-                spy(new SatelliteConfigParser(mBytesProtoBuffer));
-        doReturn(5).when(spySatelliteConfigParserAfter).getVersion();
-        doReturn(spySatelliteConfigParserAfter).when(spyTelephonyConfigUpdateInstallReceiver)
-                .getNewConfigParser(any(), any());
-
+        doReturn(true).when(spyTelephonyConfigUpdateInstallReceiver)
+                .copySourceFileToTargetFile(any(), any());
         replaceInstance(TelephonyConfigUpdateInstallReceiver.class, "sReceiverAdaptorInstance",
                 null, spyTelephonyConfigUpdateInstallReceiver);
 
         assertSame(spyTelephonyConfigUpdateInstallReceiver,
                 TelephonyConfigUpdateInstallReceiver.getInstance());
 
+        // valid config data case
+        // mVersion:4 | mSupportedServicesPerCarrier:{1={310160=[1, 2, 3], 310220=[3]}} |
+        // mSatelliteRegionCountryCodes:[US] | mIsSatelliteRegionAllowed:true | s2CellFile size:10
+        String mBase64StrForPBByteArray =
+                "CjYIBBIeCAESDgoGMzEwMTYwEAEQAhADEgoKBjMxMDIyMBADGhIKCjAxMjM0NTY3ODkSAlVTGAE=";
+        byte[] mBytesProtoBuffer = Base64.getDecoder().decode(mBase64StrForPBByteArray);
+        doReturn(mBytesProtoBuffer).when(
+                spyTelephonyConfigUpdateInstallReceiver).getContentFromContentPath(any());
+
+        // mock UpdatedParser
+        SatelliteConfigParser spyValidParser =
+                spy(new SatelliteConfigParser(mBytesProtoBuffer));
+
         ConcurrentHashMap<Executor, ConfigProviderAdaptor.Callback> spyCallbackHashMap = spy(
                 new ConcurrentHashMap<>());
         spyCallbackHashMap.put(mExecutor, mCallback);
         spyTelephonyConfigUpdateInstallReceiver.setCallbackMap(spyCallbackHashMap);
+
         spyTelephonyConfigUpdateInstallReceiver.postInstall(mContext, new Intent());
 
-        verify(spyCallbackHashMap, atLeast(1)).keySet();
+        verify(spyCallbackHashMap, times(2)).keySet();
+        verify(spyTelephonyConfigUpdateInstallReceiver, times(1))
+                .copySourceFileToTargetFile(any(), any());
+        Mockito.clearInvocations(spyCallbackHashMap);
+        Mockito.clearInvocations(spyTelephonyConfigUpdateInstallReceiver);
+
+        replaceInstance(TelephonyConfigUpdateInstallReceiver.class, "mConfigParser",
+                spyTelephonyConfigUpdateInstallReceiver, spyValidParser);
+
+        // valid config data but smaller version case
+        // mVersion:3 | mSupportedServicesPerCarrier:{1={12345=[1, 2]}} |
+        // mSatelliteRegionCountryCodes:[US] | mIsSatelliteRegionAllowed:true | s2CellFile size:10
+        mBase64StrForPBByteArray =
+                "CicIAxIPCAESCwoFMTIzNDUQARACGhIKCjAxMjM0NTY3ODkSAlVTGAE=";
+        mBytesProtoBuffer = Base64.getDecoder().decode(mBase64StrForPBByteArray);
+
+        // mock UpdatedParser
+        SatelliteConfigParser spyInvalidParser =
+                spy(new SatelliteConfigParser(mBytesProtoBuffer));
+        doReturn(spyInvalidParser).when(spyTelephonyConfigUpdateInstallReceiver)
+                .getNewConfigParser(any(), any());
+
+        spyTelephonyConfigUpdateInstallReceiver.postInstall(mContext, new Intent());
+
+        verify(spyCallbackHashMap, times(0)).keySet();
+        verify(spyTelephonyConfigUpdateInstallReceiver, times(0))
+                .copySourceFileToTargetFile(any(), any());
+        Mockito.clearInvocations(spyCallbackHashMap);
+        Mockito.clearInvocations(spyTelephonyConfigUpdateInstallReceiver);
+
+        // Empty config data case which is valid
+        // mSupportedServicesPerCarrier:{} | mSatelliteRegionCountryCodes:[US] |
+        // mIsSatelliteRegionAllowed:true | s2CellFile size:30
+        mBase64StrForPBByteArray =
+                "CioIDBomCh4wMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkSAlVTGAE=";
+        mBytesProtoBuffer = Base64.getDecoder().decode(mBase64StrForPBByteArray);
+        doReturn(mBytesProtoBuffer).when(
+                spyTelephonyConfigUpdateInstallReceiver).getContentFromContentPath(any());
+
+        // mock UpdatedParser
+        SatelliteConfigParser spyValidEmptyParser =
+                spy(new SatelliteConfigParser(mBytesProtoBuffer));
+        doReturn(spyValidEmptyParser).when(spyTelephonyConfigUpdateInstallReceiver)
+                .getNewConfigParser(any(), any());
+
+        spyTelephonyConfigUpdateInstallReceiver.postInstall(mContext, new Intent());
+        verify(spyCallbackHashMap, times(2)).keySet();
+        verify(spyTelephonyConfigUpdateInstallReceiver, times(1))
+                .copySourceFileToTargetFile(any(), any());
+        Mockito.clearInvocations(spyCallbackHashMap);
+        Mockito.clearInvocations(spyTelephonyConfigUpdateInstallReceiver);
+
+        // Wrong plmn("1234") config data case
+        // mSupportedServicesPerCarrier:{1={"1234"=[1, 2, 3]}} |
+        // mSatelliteRegionCountryCodes:[US]
+        // | mIsSatelliteRegionAllowed:true | s2CellFile size:10
+        mBase64StrForPBByteArray =
+                "CigIDBIQCAESDAoEMTIzNBABEAIQAxoSCgowMTIzNDU2Nzg5EgJVUxgB";
+        mBytesProtoBuffer = Base64.getDecoder().decode(mBase64StrForPBByteArray);
+        doReturn(mBytesProtoBuffer).when(
+                spyTelephonyConfigUpdateInstallReceiver).getContentFromContentPath(any());
+
+        // mock UpdatedParser
+        spyInvalidParser =
+                spy(new SatelliteConfigParser(mBytesProtoBuffer));
+        doReturn(spyInvalidParser).when(spyTelephonyConfigUpdateInstallReceiver)
+                .getNewConfigParser(any(), any());
+
+        spyTelephonyConfigUpdateInstallReceiver.postInstall(mContext, new Intent());
+
+        verify(spyCallbackHashMap, times(0)).keySet();
+        verify(spyTelephonyConfigUpdateInstallReceiver, times(0))
+                .copySourceFileToTargetFile(any(), any());
+        Mockito.clearInvocations(spyCallbackHashMap);
+        Mockito.clearInvocations(spyTelephonyConfigUpdateInstallReceiver);
+
+        // Wrong service("8") config data case
+        // mSupportedServicesPerCarrier:{1={12345=[6, "8"]}} |
+        // mSatelliteRegionCountryCodes:[US] |
+        // mIsSatelliteRegionAllowed:true | s2CellFile size:10
+        mBase64StrForPBByteArray =
+                "CicIDBIPCAESCwoFMTIzNDUQBhAIGhIKCjAxMjM0NTY3ODkSAlVTGAE=";
+        mBytesProtoBuffer = Base64.getDecoder().decode(mBase64StrForPBByteArray);
+        doReturn(mBytesProtoBuffer).when(
+                spyTelephonyConfigUpdateInstallReceiver).getContentFromContentPath(any());
+
+        // mock UpdatedParser
+        spyInvalidParser =
+                spy(new SatelliteConfigParser(mBytesProtoBuffer));
+        doReturn(spyInvalidParser).when(spyTelephonyConfigUpdateInstallReceiver)
+                .getNewConfigParser(any(), any());
+
+        spyTelephonyConfigUpdateInstallReceiver.postInstall(mContext, new Intent());
+
+        verify(spyCallbackHashMap, times(0)).keySet();
+        verify(spyTelephonyConfigUpdateInstallReceiver, times(0))
+                .copySourceFileToTargetFile(any(), any());
+        Mockito.clearInvocations(spyCallbackHashMap);
+        Mockito.clearInvocations(spyTelephonyConfigUpdateInstallReceiver);
     }
 
 
@@ -133,7 +241,7 @@
                 spy(new TelephonyConfigUpdateInstallReceiver());
 
         doReturn(null).when(
-                spyTelephonyConfigUpdateInstallReceiver).getCurrentContent();
+                spyTelephonyConfigUpdateInstallReceiver).getContentFromContentPath(any());
 
         replaceInstance(TelephonyConfigUpdateInstallReceiver.class, "sReceiverAdaptorInstance",
                 null, spyTelephonyConfigUpdateInstallReceiver);
@@ -144,7 +252,7 @@
                 "CjYIBBIeCAESDgoGMzEwMTYwEAEQAhADEgoKBjMxMDIyMBADGhIKCjAxMjM0NTY3ODkSAlVTGAE=";
         byte[] mBytesProtoBuffer = Base64.getDecoder().decode(mBase64StrForPBByteArray);
         doReturn(mBytesProtoBuffer).when(
-                spyTelephonyConfigUpdateInstallReceiver).getCurrentContent();
+                spyTelephonyConfigUpdateInstallReceiver).getContentFromContentPath(any());
 
         replaceInstance(TelephonyConfigUpdateInstallReceiver.class, "sReceiverAdaptorInstance",
                 null, spyTelephonyConfigUpdateInstallReceiver);
@@ -172,4 +280,59 @@
         testReceiver.unregisterCallback(testCallback);
         assertEquals(0, testReceiver.getCallbackMap().size());
     }
+
+    @Test
+    public void testIsValidSatelliteCarrierConfigData() {
+        TelephonyConfigUpdateInstallReceiver spyTelephonyConfigUpdateInstallReceiver =
+                spy(new TelephonyConfigUpdateInstallReceiver());
+        SatelliteConfigParser mockParser = mock(SatelliteConfigParser.class);
+        SatelliteConfig mockConfig = mock(SatelliteConfig.class);
+        doReturn(new ArraySet<>()).when(mockConfig).getAllSatelliteCarrierIds();
+        doReturn(mockConfig).when(mockParser).getConfig();
+
+        assertTrue(spyTelephonyConfigUpdateInstallReceiver
+                .isValidSatelliteCarrierConfigData(mockParser));
+
+        doReturn(Set.of(1)).when(mockConfig).getAllSatelliteCarrierIds();
+        Map<String, Set<Integer>> validPlmnsServices = new HashMap<>();
+        validPlmnsServices.put("123456", Set.of(FIRST_SERVICE_TYPE, 3, LAST_SERVICE_TYPE));
+        validPlmnsServices.put("12345", Set.of(FIRST_SERVICE_TYPE, 4, LAST_SERVICE_TYPE));
+        doReturn(validPlmnsServices).when(mockConfig).getSupportedSatelliteServices(anyInt());
+        doReturn(mockConfig).when(mockParser).getConfig();
+
+        assertTrue(spyTelephonyConfigUpdateInstallReceiver
+                .isValidSatelliteCarrierConfigData(mockParser));
+
+        doReturn(Set.of(1)).when(mockConfig).getAllSatelliteCarrierIds();
+        Map<String, Set<Integer>> invalidPlmnsServices1 = new HashMap<>();
+        invalidPlmnsServices1.put("123456", Set.of(FIRST_SERVICE_TYPE - 1, 3, LAST_SERVICE_TYPE));
+        doReturn(invalidPlmnsServices1).when(mockConfig).getSupportedSatelliteServices(anyInt());
+        doReturn(mockConfig).when(mockParser).getConfig();
+        assertFalse(spyTelephonyConfigUpdateInstallReceiver
+                .isValidSatelliteCarrierConfigData(mockParser));
+
+        doReturn(Set.of(1)).when(mockConfig).getAllSatelliteCarrierIds();
+        Map<String, Set<Integer>> invalidPlmnsServices2 = new HashMap<>();
+        invalidPlmnsServices2.put("123456", Set.of(FIRST_SERVICE_TYPE, 3, LAST_SERVICE_TYPE + 1));
+        doReturn(invalidPlmnsServices2).when(mockConfig).getSupportedSatelliteServices(anyInt());
+        doReturn(mockConfig).when(mockParser).getConfig();
+        assertFalse(spyTelephonyConfigUpdateInstallReceiver
+                .isValidSatelliteCarrierConfigData(mockParser));
+
+        doReturn(Set.of(1)).when(mockConfig).getAllSatelliteCarrierIds();
+        Map<String, Set<Integer>> invalidPlmnsServices3 = new HashMap<>();
+        invalidPlmnsServices3.put("1234", Set.of(FIRST_SERVICE_TYPE, 3, LAST_SERVICE_TYPE));
+        doReturn(invalidPlmnsServices3).when(mockConfig).getSupportedSatelliteServices(anyInt());
+        doReturn(mockConfig).when(mockParser).getConfig();
+        assertFalse(spyTelephonyConfigUpdateInstallReceiver
+                .isValidSatelliteCarrierConfigData(mockParser));
+
+        doReturn(Set.of(1)).when(mockConfig).getAllSatelliteCarrierIds();
+        Map<String, Set<Integer>> invalidPlmnsServices4 = new HashMap<>();
+        invalidPlmnsServices4.put("1234567", Set.of(FIRST_SERVICE_TYPE, 3, LAST_SERVICE_TYPE));
+        doReturn(invalidPlmnsServices4).when(mockConfig).getSupportedSatelliteServices(anyInt());
+        doReturn(mockConfig).when(mockParser).getConfig();
+        assertFalse(spyTelephonyConfigUpdateInstallReceiver
+                .isValidSatelliteCarrierConfigData(mockParser));
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java
index d1e5066..e45023c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/AccessNetworksManagerTest.java
@@ -356,7 +356,7 @@
 
         mAccessNetworksManager.registerCallback(mMockedCallback);
 
-        mQnsCallback.onReconnectQualifedNetworkType(ApnSetting.TYPE_IMS | ApnSetting.TYPE_MMS,
+        mQnsCallback.onReconnectQualifiedNetworkType(ApnSetting.TYPE_IMS | ApnSetting.TYPE_MMS,
                 AccessNetworkType.IWLAN);
         processAllMessages();
 
@@ -381,7 +381,7 @@
     @Test
     public void testCallbackForReconnectQualifiedNetworkTypeWithFlagDisabled() throws Exception {
         when(mFeatureFlags.reconnectQualifiedNetwork()).thenReturn(false);
-        mQnsCallback.onReconnectQualifedNetworkType(ApnSetting.TYPE_IMS | ApnSetting.TYPE_MMS,
+        mQnsCallback.onReconnectQualifiedNetworkType(ApnSetting.TYPE_IMS | ApnSetting.TYPE_MMS,
                 AccessNetworkType.IWLAN);
         processAllMessages();
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
index ddbe9c0..0e2676e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/AutoDataSwitchControllerTest.java
@@ -93,8 +93,10 @@
     private TelephonyDisplayInfo mGoodTelephonyDisplayInfo;
     private TelephonyDisplayInfo mBadTelephonyDisplayInfo;
     private int mDefaultDataSub;
+    private DataEvaluation mDataEvaluation;
     private AutoDataSwitchController mAutoDataSwitchControllerUT;
     private Map<Integer, AlarmManager.OnAlarmListener> mEventsToAlarmListener;
+    private Map<Integer, Object> mScheduledEventsToExtras;
     @Before
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
@@ -137,6 +139,8 @@
             doAnswer(invocation -> phone.getSubId() == mDefaultDataSub)
                     .when(phone).isUserDataEnabled();
         }
+        mDataEvaluation = new DataEvaluation(DataEvaluation.DataEvaluationReason.EXTERNAL_QUERY);
+        doReturn(mDataEvaluation).when(mDataNetworkController).getInternetEvaluation(anyBoolean());
         doReturn(new int[]{SUB_1, SUB_2}).when(mSubscriptionManagerService)
                 .getActiveSubIdList(true);
         doAnswer(invocation -> {
@@ -184,9 +188,12 @@
                 mAutoDataSwitchControllerUT, mMockedAlarmManager);
         mEventsToAlarmListener = getPrivateField(mAutoDataSwitchControllerUT,
                 "mEventsToAlarmListener", Map.class);
+        mScheduledEventsToExtras = getPrivateField(mAutoDataSwitchControllerUT,
+                "mScheduledEventsToExtras", Map.class);
 
         doReturn(true).when(mFeatureFlags).autoDataSwitchAllowRoaming();
         doReturn(true).when(mFeatureFlags).carrierEnabledSatelliteFlag();
+        doReturn(true).when(mFeatureFlags).autoDataSwitchUsesDataEnabled();
     }
 
     @After
@@ -240,7 +247,10 @@
         prepareIdealUsesNonDdsCondition();
         processAllFutureMessages();
         clearInvocations(mMockedPhoneSwitcherCallback);
-        doReturn(false).when(mPhone2).isDataAllowed();
+        mDataEvaluation.addDataDisallowedReason(DataEvaluation.DataDisallowedReason
+                .NO_SUITABLE_DATA_PROFILE);
+        doReturn(mDataEvaluation)
+                .when(mDataNetworkController).getInternetEvaluation(anyBoolean());
         mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED);
         processAllFutureMessages();
 
@@ -350,7 +360,6 @@
 
     @Test
     public void testRoaming_same_roaming_condition_uses_rat_signalStrength() {
-        doReturn(true).when(mFeatureFlags).autoDataSwitchRatSs();
         // On primary phone
         // 1. Both roaming, user allow roaming on both phone, uses RAT score to decide switch.
         prepareIdealUsesNonDdsCondition();
@@ -375,7 +384,6 @@
 
     @Test
     public void testCancelSwitch_onPrimary_rat_signalStrength() {
-        doReturn(true).when(mFeatureFlags).autoDataSwitchRatSs();
         // 4.1.1 Display info and signal strength on secondary phone became bad,
         // but primary is still OOS, so still switch to the secondary.
         prepareIdealUsesNonDdsCondition();
@@ -467,7 +475,7 @@
         prepareIdealUsesNonDdsCondition();
         // 2.2 Auto switch feature is disabled, no need validation
         clearInvocations(mCellularNetworkValidator);
-        doReturn(false).when(mPhone2).isDataAllowed();
+        mDataEvaluation.addDataDisallowedReason(DataEvaluation.DataDisallowedReason.DATA_DISABLED);
         mAutoDataSwitchControllerUT.evaluateAutoDataSwitch(EVALUATION_REASON_DATA_SETTINGS_CHANGED);
         processAllFutureMessages();
 
@@ -483,11 +491,13 @@
 
         verify(mMockedPhoneSwitcherCallback).onRequireValidation(DEFAULT_PHONE_INDEX,
                 false/*needValidation*/);
+
+        clearInvocations(mMockedPhoneSwitcherCallback);
+        prepareIdealUsesNonDdsCondition();
     }
 
     @Test
     public void testOnNonDdsSwitchBackToPrimary_rat_signalStrength() {
-        doReturn(true).when(mFeatureFlags).autoDataSwitchRatSs();
         prepareIdealUsesNonDdsCondition();
         processAllFutureMessages();
         doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
@@ -536,10 +546,16 @@
 
     @Test
     public void testStabilityCheckOverride_basic() {
+        // Disable RAT + signalStrength base switching.
+        doReturn(-1).when(mDataConfigManager).getAutoDataSwitchScoreTolerance();
+        mAutoDataSwitchControllerUT = new AutoDataSwitchController(mContext, Looper.myLooper(),
+                mPhoneSwitcher, mFeatureFlags, mMockedPhoneSwitcherCallback);
+
         // Starting stability check for switching to non-DDS
         prepareIdealUsesNonDdsCondition();
-        processAllMessages();
+        processAllFutureMessages();
 
+        clearInvocations(mMockedPhoneSwitcherCallback);
         // Switch success, but the previous stability check is still pending
         doReturn(PHONE_2).when(mPhoneSwitcher).getPreferredDataPhoneId();
 
@@ -557,7 +573,6 @@
 
     @Test
     public void testStabilityCheckOverride_uses_rat_signalStrength() {
-        doReturn(true).when(mFeatureFlags).autoDataSwitchRatSs();
         // Switching due to availability first.
         prepareIdealUsesNonDdsCondition();
 
@@ -578,6 +593,7 @@
     public void testValidationFailedRetry() {
         prepareIdealUsesNonDdsCondition();
 
+        clearInvocations(mMockedPhoneSwitcherCallback);
         for (int i = 0; i < MAX_RETRY; i++) {
             mAutoDataSwitchControllerUT.evaluateRetryOnValidationFailed();
             processAllFutureMessages();
@@ -730,7 +746,7 @@
 
         // 4.2 Auto switch feature is enabled
         doReturn(true).when(mPhone2).getDataRoamingEnabled();
-        doReturn(true).when(mPhone2).isDataAllowed();
+        mDataEvaluation.addDataAllowedReason(DataEvaluation.DataAllowedReason.NORMAL);
 
         // 5. No default network
         mAutoDataSwitchControllerUT.updateDefaultNetworkCapabilities(null /*networkCapabilities*/);
@@ -783,10 +799,12 @@
 
     @Override
     public void processAllFutureMessages() {
-        if (mFeatureFlags.autoDataSwitchRatSs()
-                && mEventsToAlarmListener.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
+        if (mScheduledEventsToExtras.containsKey(EVENT_STABILITY_CHECK_PASSED)) {
             mEventsToAlarmListener.get(EVENT_STABILITY_CHECK_PASSED).onAlarm();
         }
+        if (mScheduledEventsToExtras.containsKey(EVENT_EVALUATE_AUTO_SWITCH)) {
+            mEventsToAlarmListener.get(EVENT_EVALUATE_AUTO_SWITCH).onAlarm();
+        }
         super.processAllFutureMessages();
     }
 }
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 9423551..60dcb59 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
@@ -51,6 +51,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkRequest;
+import android.net.Uri;
 import android.net.vcn.VcnManager.VcnNetworkPolicyChangeListener;
 import android.net.vcn.VcnNetworkPolicyResult;
 import android.os.AsyncResult;
@@ -138,6 +139,8 @@
 import java.util.Set;
 import java.util.concurrent.Executor;
 
+import javax.annotation.Nullable;
+
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class DataNetworkControllerTest extends TelephonyTest {
@@ -182,8 +185,6 @@
     private LinkBandwidthEstimatorCallback mLinkBandwidthEstimatorCallback;
 
     private boolean mIsNonTerrestrialNetwork = false;
-    private ArrayList<Integer> mCarrierSupportedSatelliteServices = new ArrayList<>();
-    private FeatureFlags mFeatureFlags;
 
     private final DataProfile mGeneralPurposeDataProfile = new DataProfile.Builder()
             .setApnSetting(new ApnSetting.Builder()
@@ -393,17 +394,6 @@
                             "PRIORITIZE_LATENCY", 1).getBytes()))
             .build();
 
-    private final DataProfile mMmsOnWlanDataProfile = new DataProfile.Builder()
-            .setApnSetting(new ApnSetting.Builder()
-                    .setEntryName("mms_wlan")
-                    .setApnName("mms_wlan")
-                    .setApnTypeBitmask(ApnSetting.TYPE_MMS)
-                    .setCarrierEnabled(true)
-                    .setNetworkTypeBitmask((int) TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN)
-                    .build())
-            .setPreferred(false)
-            .build();
-
     private final DataProfile mNtnDataProfile = new DataProfile.Builder()
             .setApnSetting(new ApnSetting.Builder()
                     .setEntryName("ntn")
@@ -700,7 +690,6 @@
                 .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
                 .setDataSpecificInfo(dsri)
                 .setIsNonTerrestrialNetwork(mIsNonTerrestrialNetwork)
-                .setAvailableServices(mCarrierSupportedSatelliteServices)
                 .setEmergencyOnly(isEmergencyOnly)
                 .build());
 
@@ -710,7 +699,6 @@
                 .setRegistrationState(iwlanRegState)
                 .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
                 .setIsNonTerrestrialNetwork(mIsNonTerrestrialNetwork)
-                .setAvailableServices(mCarrierSupportedSatelliteServices)
                 .setEmergencyOnly(isEmergencyOnly)
                 .build());
 
@@ -854,6 +842,11 @@
                 .config_enable_iwlan_handover_policy, true);
         mContextFixture.putBooleanResource(com.android.internal.R.bool
                 .config_enhanced_iwlan_handover_check, true);
+        mContextFixture.putStringArrayResource(com.android.internal.R.array
+                .config_force_cellular_transport_capabilities,
+                new String[] {"ims", "eims", "xcap"});
+        mContextFixture.putIntResource(com.android.internal.R.integer
+                .config_reevaluate_bootstrap_sim_data_usage_millis, 60000);
     }
 
     @Before
@@ -870,7 +863,6 @@
         mMockedDataNetworkControllerCallback = Mockito.mock(DataNetworkControllerCallback.class);
         mMockedDataRetryManagerCallback = Mockito.mock(DataRetryManagerCallback.class);
         mMockSubInfo = Mockito.mock(SubscriptionInfo.class);
-        mFeatureFlags = Mockito.mock(FeatureFlags.class);
         when(mTelephonyComponentFactory.makeDataSettingsManager(any(Phone.class),
                 any(DataNetworkController.class), any(FeatureFlags.class), any(Looper.class),
                 any(DataSettingsManager.DataSettingsManagerCallback.class))).thenCallRealMethod();
@@ -896,6 +888,10 @@
         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();
 
         List<SubscriptionInfo> infoList = new ArrayList<>();
         infoList.add(mMockSubInfo);
@@ -997,7 +993,7 @@
         List<DataProfile> profiles = List.of(mGeneralPurposeDataProfile,
                 mGeneralPurposeDataProfileAlternative, mImsCellularDataProfile,
                 mImsIwlanDataProfile, mEmergencyDataProfile, mFotaDataProfile,
-                mTetheringDataProfile, mMmsOnWlanDataProfile, mLowLatencyDataProfile,
+                mTetheringDataProfile, mLowLatencyDataProfile,
                 mNtnDataProfile, mEsimBootstrapDataProfile,
                 mEsimBootstrapImsProfile, mEsimBootstrapRcsInfraStructureProfile);
 
@@ -1136,15 +1132,29 @@
     }
 
     private @NonNull TelephonyNetworkRequest createNetworkRequest(Integer... capabilities) {
+        return createNetworkRequest(null, capabilities);
+    }
+
+    private @NonNull TelephonyNetworkRequest createNetworkRequest(@Nullable Boolean restricted,
+                                                                  Integer... capabilities) {
         NetworkCapabilities netCaps = new NetworkCapabilities();
         for (int networkCapability : capabilities) {
             netCaps.addCapability(networkCapability);
         }
 
+        if (restricted != null) {
+            if (restricted) {
+                netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+            }
+        } else {
+            // Data Network uses the same to define its own capabilities.
+            netCaps.maybeMarkCapabilitiesRestricted();
+        }
+
         NetworkRequest nativeNetworkRequest = new NetworkRequest(netCaps,
                 ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId, NetworkRequest.Type.REQUEST);
 
-        return new TelephonyNetworkRequest(nativeNetworkRequest, mPhone);
+        return new TelephonyNetworkRequest(nativeNetworkRequest, mPhone, mFeatureFlags);
     }
 
     // The purpose of this test is to make sure the network request insertion/removal works as
@@ -1405,7 +1415,7 @@
         netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
         mDataNetworkControllerUT.addNetworkRequest(new TelephonyNetworkRequest(
                 new NetworkRequest(netCaps, ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
-                        NetworkRequest.Type.REQUEST), mPhone));
+                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags));
         processAllMessages();
         verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE);
         List<DataNetwork> dataNetworkList = getDataNetworks();
@@ -1708,72 +1718,232 @@
     }
 
     @Test
-    public void testNonTerrestrialNetworkChangedWithoutDataSupport() throws Exception {
-        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+    public void testIsNetworkRequestSatisfiedByTransportCellularTransportRequest() {
         mIsNonTerrestrialNetwork = true;
-        // Data is not supported while using satellite
-        mCarrierSupportedSatelliteServices.add(NetworkRegistrationInfo.SERVICE_TYPE_VOICE);
+
+        // Data is not supported for cellular transport network request while using satellite
+        // network
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
 
-        mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        // Set network request transport as Cellular in satellite network
+        NetworkCapabilities netCaps = new NetworkCapabilities();
+        netCaps.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        mDataNetworkControllerUT.addNetworkRequest(new TelephonyNetworkRequest(
+                new NetworkRequest(netCaps, ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags));
         processAllMessages();
 
-        // Data with internet capability should not be allowed
-        // when the device is using non-terrestrial network
-        verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        // Verify data is not connected since Network request cannot satisfy by transport
+        verify(mMockedDataNetworkControllerCallback, never())
+                .onConnectedInternetDataNetworksChanged(any());
+
+        // However, WLAN network setup shouldn't be affected
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
+                .getPreferredTransportByNetworkCapability(anyInt());
+        mDataNetworkControllerUT.obtainMessage(5 /*EVENT_REEVALUATE_UNSATISFIED_NETWORK_REQUESTS*/,
+                DataEvaluation.DataEvaluationReason.PREFERRED_TRANSPORT_CHANGED).sendToTarget();
+        processAllMessages();
+        verify(mMockedDataNetworkControllerCallback).onConnectedInternetDataNetworksChanged(any());
 
         mIsNonTerrestrialNetwork = false;
-        mCarrierSupportedSatelliteServices.clear();
-        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
-                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
-
-        // Verify data is restored.
-        verifyInternetConnected();
     }
 
     @Test
-    public void testNonTerrestrialNetworkWithDataSupport() throws Exception {
-        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+    public void testMobileDataDisabledIsValidRestrictedRequestWithSatelliteInternetRequest() {
         mIsNonTerrestrialNetwork = true;
-        // Data is supported while using satellite
-        mCarrierSupportedSatelliteServices.add(NetworkRegistrationInfo.SERVICE_TYPE_DATA);
+
+        //Mobile Data Disabled
+        mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled(
+                TelephonyManager.DATA_ENABLED_REASON_USER, false, mContext.getOpPackageName());
+        processAllMessages();
+
+        // Data is not supported for cellular transport network request while using satellite
+        // network
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
 
-        mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        // Set network request transport as Satellite with restricted capability + internet
+        NetworkCapabilities netCaps = new NetworkCapabilities();
+        netCaps.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        mDataNetworkControllerUT.addNetworkRequest(new TelephonyNetworkRequest(
+                new NetworkRequest(netCaps, ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags));
         processAllMessages();
 
-        // Verify data is connected.
-        verifyInternetConnected();
+        // Verify data is not connected since Network request cannot satisfy by transport
+        verify(mMockedDataNetworkControllerCallback, never())
+                .onConnectedInternetDataNetworksChanged(any());
 
         mIsNonTerrestrialNetwork = false;
-        mCarrierSupportedSatelliteServices.clear();
     }
 
     @Test
-    public void testNonTerrestrialNetworkWithFlagDisabled() throws Exception {
-        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
-
+    public void testMobileDataDisabledIsValidRestrictedRequestWithTransportSatelliteMMSRequest()
+            throws Exception {
         mIsNonTerrestrialNetwork = true;
-        // Data is not supported while using satellite
-        mCarrierSupportedSatelliteServices.add(NetworkRegistrationInfo.SERVICE_TYPE_VOICE);
+
+        // Data disabled
+        mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled(
+                TelephonyManager.DATA_ENABLED_REASON_USER, false, mContext.getOpPackageName());
+        // Always allow MMS off
+        mDataNetworkControllerUT.getDataSettingsManager().setMobileDataPolicy(TelephonyManager
+                .MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED, false);
+        processAllMessages();
+
+        // Data is not supported for cellular transport network request while using satellite
+        // network
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
 
-        mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        // Set network request transport as Cellular+Satellite with restricted capability + mms
+        NetworkCapabilities netCaps = new NetworkCapabilities();
+        netCaps.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+        netCaps.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
+        netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        mDataNetworkControllerUT.addNetworkRequest(new TelephonyNetworkRequest(
+                new NetworkRequest(netCaps, ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags));
         processAllMessages();
 
-        // As feature is disabled, data is connected.
-        verifyInternetConnected();
+        // Verify mms is not connected
+        verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
 
         mIsNonTerrestrialNetwork = false;
-        mCarrierSupportedSatelliteServices.clear();
     }
 
+    @Test
+    public void testOnMmsAlwaysALlowedIsValidRestrictedRequestWithTransportSatelliteMMSRequest()
+            throws Exception {
+        mIsNonTerrestrialNetwork = true;
+
+        // Data disabled
+        mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled(
+                TelephonyManager.DATA_ENABLED_REASON_USER, false, mContext.getOpPackageName());
+        // Always allow MMS On
+        mDataNetworkControllerUT.getDataSettingsManager().setMobileDataPolicy(TelephonyManager
+                .MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED, true);
+        processAllMessages();
+
+        // Data is not supported for cellular transport network request while using satellite
+        // network
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+
+        // Set network request transport as Cellular+Satellite with restricted capability + mms
+        NetworkCapabilities netCaps = new NetworkCapabilities();
+        netCaps.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+        netCaps.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
+        netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        mDataNetworkControllerUT.addNetworkRequest(new TelephonyNetworkRequest(
+                new NetworkRequest(netCaps, ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags));
+        processAllMessages();
+
+        // Verify mms is connected if mms always allowed is on
+        verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_MMS);
+
+        mIsNonTerrestrialNetwork = false;
+    }
+
+    @Test
+    public void testIsNetworkRequestSatisfiedByTransportSatelliteTransportRequest_Terrestrial() {
+        // Set network request transport as satellite in satellite network
+        NetworkCapabilities netCaps = new NetworkCapabilities();
+        netCaps.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        mDataNetworkControllerUT.addNetworkRequest(new TelephonyNetworkRequest(
+                new NetworkRequest(netCaps, ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags));
+        processAllMessages();
+
+        // Verify data is not supported for satellite transport network request while using cellular
+        verify(mMockedDataNetworkControllerCallback, never())
+                .onConnectedInternetDataNetworksChanged(any());
+
+    }
+
+    @Test
+    public void testIsNetworkRequestSatisfiedByTransportSatelliteTransportRequest() {
+        mIsNonTerrestrialNetwork = true;
+
+        // Data is supported for satellite transport network request while using satellite network
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+
+        // Set network request transport as satellite while using satellite network
+        NetworkCapabilities netCaps = new NetworkCapabilities();
+        netCaps.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE);
+        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        mDataNetworkControllerUT.addNetworkRequest(new TelephonyNetworkRequest(
+                new NetworkRequest(netCaps, ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags));
+        processAllMessages();
+
+        // Verify data is connected since Network request satisfy by transport
+        verify(mMockedDataNetworkControllerCallback).onConnectedInternetDataNetworksChanged(any());
+
+        mIsNonTerrestrialNetwork = false;
+    }
+
+    @Test
+    public void testIsNetworkRequestSatisfiedByTransportNoTransportRequest() {
+        mIsNonTerrestrialNetwork = true;
+
+        // Data is supported for no transport network request while using satellite network
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+
+        // Set network request transport as no transport with Internet capability
+        NetworkCapabilities netCaps = new NetworkCapabilities();
+        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        mDataNetworkControllerUT.addNetworkRequest(new TelephonyNetworkRequest(
+                new NetworkRequest(netCaps, ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags));
+        processAllMessages();
+
+        // Verify data is connected since Network request satisfy by transport
+        verify(mMockedDataNetworkControllerCallback).onConnectedInternetDataNetworksChanged(any());
+
+        mIsNonTerrestrialNetwork = false;
+    }
+
+    @Test
+    public void testIsNetworkCapabilitySatelliteAndCellularCapableImsCellularTransportRequest()
+            throws Exception {
+        mIsNonTerrestrialNetwork = true;
+
+        // IMS PDN is supported for cellular network request while using satellite network
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+
+        // Set network request transport as Cellular + IMS
+        NetworkCapabilities netCaps = new NetworkCapabilities();
+        netCaps.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
+        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL);
+        netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        mDataNetworkControllerUT.addNetworkRequest(new TelephonyNetworkRequest(
+                new NetworkRequest(netCaps, ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags));
+        processAllMessages();
+
+        // Verify ims is connected since, cellular network request for ims is allowed while using
+        // satellite network
+        verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_IMS,
+                NetworkCapabilities.NET_CAPABILITY_MMTEL);
+
+        mIsNonTerrestrialNetwork = false;
+    }
 
     @Test
     public void testRoamingDataChanged() throws Exception {
@@ -3156,12 +3326,20 @@
                 NetworkCapabilities.NET_CAPABILITY_IMS);
         TelephonyNetworkRequest secondTnr = createNetworkRequest(
                 NetworkCapabilities.NET_CAPABILITY_IMS);
+        TelephonyNetworkRequest thirdTnr = new TelephonyNetworkRequest(
+                new NetworkRequest((new NetworkCapabilities.Builder())
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
+                        .addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE)
+                        .build(),
+                        ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags);
 
         mDataNetworkControllerUT.addNetworkRequest(firstTnr);
         processAllMessages();
 
         mDataNetworkControllerUT.removeNetworkRequest(firstTnr);
         mDataNetworkControllerUT.addNetworkRequest(secondTnr);
+        mDataNetworkControllerUT.addNetworkRequest(thirdTnr);
         processAllFutureMessages();
 
         verify(mMockedWwanDataServiceManager, times(1)).setupDataCall(anyInt(),
@@ -3319,9 +3497,6 @@
         // Verify retry is cleared on this network
         assertThat(mDataNetworkControllerUT.getDataRetryManager()
                 .isAnyHandoverRetryScheduled(network)).isFalse();
-        // Verify the data profile is still throttled
-        assertThat(mDataNetworkControllerUT.getDataRetryManager().isDataProfileThrottled(
-                network.getDataProfile(), AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).isTrue();
     }
 
     @Test
@@ -3437,7 +3612,7 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
 
         mDataNetworkControllerUT.addNetworkRequest(tnr);
         processAllMessages();
@@ -3461,7 +3636,7 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
 
         mDataNetworkControllerUT.addNetworkRequest(tnr);
         processAllMessages();
@@ -3533,7 +3708,7 @@
                 ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId, NetworkRequest.Type.REQUEST);
 
         mDataNetworkControllerUT.addNetworkRequest(
-                new TelephonyNetworkRequest(nativeNetworkRequest, mPhone));
+                new TelephonyNetworkRequest(nativeNetworkRequest, mPhone, mFeatureFlags));
         processAllMessages();
 
         verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_DUN);
@@ -3649,7 +3824,7 @@
                 ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId, NetworkRequest.Type.REQUEST);
 
         mDataNetworkControllerUT.addNetworkRequest(
-                new TelephonyNetworkRequest(nativeNetworkRequest, mPhone));
+                new TelephonyNetworkRequest(nativeNetworkRequest, mPhone, mFeatureFlags));
         processAllMessages();
 
         // Everything should be disconnected.
@@ -3687,7 +3862,7 @@
                 ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId, NetworkRequest.Type.REQUEST);
 
         mDataNetworkControllerUT.addNetworkRequest(
-                new TelephonyNetworkRequest(nativeNetworkRequest, mPhone));
+                new TelephonyNetworkRequest(nativeNetworkRequest, mPhone, mFeatureFlags));
         processAllMessages();
 
         // Everything should be disconnected.
@@ -3910,7 +4085,7 @@
                 ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId, NetworkRequest.Type.REQUEST);
 
         mDataNetworkControllerUT.addNetworkRequest(
-                new TelephonyNetworkRequest(nativeNetworkRequest, mPhone));
+                new TelephonyNetworkRequest(nativeNetworkRequest, mPhone, mFeatureFlags));
         processAllMessages();
 
         verifyConnectedNetworkHasDataProfile(mGeneralPurposeDataProfile);
@@ -3934,7 +4109,7 @@
                 ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId, NetworkRequest.Type.REQUEST);
 
         mDataNetworkControllerUT.addNetworkRequest(
-                new TelephonyNetworkRequest(nativeNetworkRequest, mPhone));
+                new TelephonyNetworkRequest(nativeNetworkRequest, mPhone, mFeatureFlags));
         processAllMessages();
 
         verifyConnectedNetworkHasDataProfile(mGeneralPurposeDataProfile);
@@ -3988,18 +4163,54 @@
     }
 
     @Test
+    public void testNetworkValidationStatusChangeCallback() throws Exception {
+        // Request not restricted network
+        TelephonyNetworkRequest request = createNetworkRequest(
+                NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        mDataNetworkControllerUT.addNetworkRequest(request);
+        processAllMessages();
+        TelephonyNetworkAgent agent = getPrivateField(getDataNetworks().get(0), "mNetworkAgent",
+                TelephonyNetworkAgent.class);
+        agent.onValidationStatus(1/*status*/, Uri.EMPTY);
+        processAllMessages();
+
+        // Verify notify
+        verify(mMockedDataNetworkControllerCallback)
+                .onInternetDataNetworkValidationStatusChanged(1);
+
+        // Request restricted network
+        mDataNetworkControllerUT.removeNetworkRequest(request);
+        getDataNetworks().get(0).tearDown(DataNetwork.TEAR_DOWN_REASON_NONE);
+        mDataNetworkControllerUT.getDataSettingsManager().setDataEnabled(0, false, "");
+        processAllMessages();
+        clearInvocations(mMockedDataNetworkControllerCallback);
+        mDataNetworkControllerUT.addNetworkRequest(createNetworkRequest(
+                true, NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        processAllMessages();
+        agent = getPrivateField(getDataNetworks().get(0), "mNetworkAgent",
+                TelephonyNetworkAgent.class);
+        agent.onValidationStatus(1/*status*/, Uri.EMPTY);
+        processAllMessages();
+
+        // Verify not notified
+        verify(mMockedDataNetworkControllerCallback, never())
+                .onInternetDataNetworkValidationStatusChanged(anyInt());
+    }
+
+    @Test
     public void testImsGracefulTearDown() 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);
+                nativeNetworkRequest, mPhone, mFeatureFlags);
 
         mDataNetworkControllerUT.addNetworkRequest(networkRequest);
 
@@ -4046,12 +4257,13 @@
         // setup emergency data network.
         NetworkCapabilities netCaps = new NetworkCapabilities();
         netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS);
+        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);
+                nativeNetworkRequest, mPhone, mFeatureFlags);
 
         mDataNetworkControllerUT.addNetworkRequest(networkRequest);
         processAllMessages();
@@ -4693,11 +4905,12 @@
                 ConnectivityManager.TYPE_MOBILE, 0, NetworkRequest.Type.REQUEST);
 
         mDataNetworkControllerUT.addNetworkRequest(new TelephonyNetworkRequest(
-                nativeNetworkRequest, mPhone));
+                nativeNetworkRequest, mPhone, mFeatureFlags));
         processAllMessages();
 
         // Intentionally create a new telephony request with the original native network request.
-        TelephonyNetworkRequest request = new TelephonyNetworkRequest(nativeNetworkRequest, mPhone);
+        TelephonyNetworkRequest request = new TelephonyNetworkRequest(
+                nativeNetworkRequest, mPhone, mFeatureFlags);
 
         mDataNetworkControllerUT.removeNetworkRequest(request);
         processAllFutureMessages();
@@ -4788,7 +5001,7 @@
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
                         .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                        .build(), mPhone));
+                        .build(), mPhone, mFeatureFlags));
         processAllMessages();
         verifyConnectedNetworkHasCapabilities(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE,
                 NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY);
@@ -4797,7 +5010,7 @@
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
                         .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                        .build(), mPhone));
+                        .build(), mPhone, mFeatureFlags));
         processAllMessages();
         List<DataNetwork> dataNetworkList = getDataNetworks();
         assertThat(dataNetworkList).hasSize(1);
@@ -4838,43 +5051,85 @@
     }
 
     @Test
-    public void testAllowBringUpWithDifferentDataProfileForWlan() throws Exception {
-        // Mock MMS preferred on WLAN
-        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
-                .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
-
-        // Setup a default cellular network that's capable of MMS
-        mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
-        mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_IMS));
-        processAllMessages();
-        verifyConnectedNetworkHasDataProfile(mGeneralPurposeDataProfile);
-
-        // Mock the designated MMS profile when WLAN is preferred
-        doReturn(mMmsOnWlanDataProfile).when(mDataProfileManager).getDataProfileForNetworkRequest(
-                any(TelephonyNetworkRequest.class), eq(TelephonyManager.NETWORK_TYPE_IWLAN),
-                anyBoolean(), anyBoolean(), anyBoolean());
-        setSuccessfulSetupDataResponse(mMockedWlanDataServiceManager,
-                createDataCallResponse(2, DataCallResponse.LINK_STATUS_ACTIVE));
-
-        // Verify the designated MMS profile is used to satisfy MMS request
-        mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_MMS));
-        processAllMessages();
-        verifyConnectedNetworkHasDataProfile(mMmsOnWlanDataProfile);
-    }
-
-    @Test
     public void testNonTerrestrialNetwork() throws Exception {
-        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        TelephonyNetworkRequest request;
         mIsNonTerrestrialNetwork = true;
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
-        mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_RCS));
+        // By default, Test only support restricted network, regardless whether constrained.
+        // Verify not_restricted network not supported.
+        request = createNetworkRequest(false, NetworkCapabilities.NET_CAPABILITY_RCS);
+        mDataNetworkControllerUT.addNetworkRequest(request);
+        processAllMessages();
+        verifyAllDataDisconnected();
+        mDataNetworkControllerUT.removeNetworkRequest(request);
+
+        // Verify restricted network is supported.
+        request = createNetworkRequest(true, NetworkCapabilities.NET_CAPABILITY_RCS);
+        mDataNetworkControllerUT.addNetworkRequest(request);
         processAllMessages();
         verifyConnectedNetworkHasDataProfile(mNtnDataProfile);
+        mDataNetworkControllerUT.removeNetworkRequest(request);
+        getDataNetworks().get(0).tearDown(DataNetwork
+                .TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED);
+
+        // Test constrained network is supported, regardless whether it's restricted
+        processAllFutureMessages();
+        verifyAllDataDisconnected();
+        mCarrierConfig.putInt(CarrierConfigManager.KEY_SATELLITE_DATA_SUPPORT_MODE_INT,
+                CarrierConfigManager.SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED);
+        carrierConfigChanged();
+        // Verify not_restricted, not_constrained network not supported.
+        NetworkCapabilities netCaps = new NetworkCapabilities();
+        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_RCS);
+        try {
+            netCaps.addCapability(DataUtils.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+        } catch (Exception ignored) { }
+        request = new TelephonyNetworkRequest(new NetworkRequest(netCaps,
+                ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+                NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags);
+
+        mDataNetworkControllerUT.addNetworkRequest(request);
+        processAllMessages();
+        verifyAllDataDisconnected();
+        mDataNetworkControllerUT.removeNetworkRequest(request);
+
+        // Verify restricted, not_constrained network is supported.
+        netCaps = new NetworkCapabilities();
+        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_RCS);
+        netCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+        try {
+            netCaps.addCapability(DataUtils.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+        } catch (Exception ignored) { }
+        request = new TelephonyNetworkRequest(new NetworkRequest(netCaps,
+                ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+                NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags);
+        mDataNetworkControllerUT.addNetworkRequest(request);
+        processAllMessages();
+        verifyConnectedNetworkHasDataProfile(mNtnDataProfile);
+        mDataNetworkControllerUT.removeNetworkRequest(request);
+        getDataNetworks().get(0).tearDown(DataNetwork
+                .TEAR_DOWN_REASON_CONNECTIVITY_SERVICE_UNWANTED);
+
+        // TODO(enable after NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED become a default cap)
+        // Test not constrained network supported
+//        processAllFutureMessages();
+//        verifyAllDataDisconnected();
+//        mCarrierConfig.putInt(CarrierConfigManager.KEY_SATELLITE_DATA_SUPPORT_MODE_INT,
+//                CarrierConfigManager.SATELLITE_DATA_SUPPORT_ALL);
+//        carrierConfigChanged();
+//
+//        netCaps = new NetworkCapabilities();
+//        netCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_RCS);
+//        try {
+//            netCaps.addCapability(DataUtils.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+//        } catch (Exception ignored) {}
+//        mDataNetworkControllerUT.addNetworkRequest(
+//                new TelephonyNetworkRequest(new NetworkRequest(netCaps,
+//                        ConnectivityManager.TYPE_MOBILE, ++mNetworkRequestId,
+//                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags));
+//        processAllMessages();
+//        verifyConnectedNetworkHasDataProfile(mNtnDataProfile);
     }
 
     @Test
@@ -4929,7 +5184,8 @@
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+                createNetworkRequest(true/*restricted*/,
+                        NetworkCapabilities.NET_CAPABILITY_INTERNET));
         processAllMessages();
         verifyConnectedNetworkHasDataProfile(mEsimBootstrapDataProfile);
 
@@ -4940,13 +5196,6 @@
                 .get(AccessNetworkConstants.TRANSPORT_TYPE_WWAN), 2);
         processAllMessages();
         verifyConnectedNetworkHasDataProfile(mEsimBootstrapImsProfile);
-
-        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
-                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
-        mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_RCS));
-        processAllMessages();
-        verifyNoConnectedNetworkHasCapability(NetworkCapabilities.NET_CAPABILITY_RCS);
     }
 
     @Test
@@ -4985,7 +5234,7 @@
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+                createNetworkRequest(true, NetworkCapabilities.NET_CAPABILITY_INTERNET));
         processAllMessages();
         // With allowed data limit unlimited, connection is allowed
         verifyConnectedNetworkHasDataProfile(mEsimBootstrapDataProfile);
@@ -5031,8 +5280,7 @@
     }
 
     @Test
-    public void testNtnNetworkOnProvisioningProfileClass_WithFlagEnabled() throws Exception {
-        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+    public void testNtnNetworkOnProvisioningProfileClassWithFlagEnabled() throws Exception {
         when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
         // Allowed data limit Unlimited
         mContextFixture.putIntResource(com.android.internal.R.integer
@@ -5044,7 +5292,7 @@
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
                 NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         mDataNetworkControllerUT.addNetworkRequest(
-                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_RCS));
+                createNetworkRequest(true, NetworkCapabilities.NET_CAPABILITY_RCS));
         processAllMessages();
 
         assertThat(mDataNetworkControllerUT.isEsimBootStrapProvisioningActivated()).isTrue();
@@ -5053,8 +5301,7 @@
     }
 
     @Test
-    public void testNonNtnNetworkOnProvisioningProfileClass_WithFlagEnabled() throws Exception {
-        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+    public void testNonNtnNetworkOnProvisioningProfileClassWithFlagEnabled() throws Exception {
         when(mFeatureFlags.esimBootstrapProvisioningFlag()).thenReturn(true);
         doReturn(new SubscriptionInfoInternal.Builder().setId(1)
                 .setProfileClass(SubscriptionManager.PROFILE_CLASS_PROVISIONING).build())
@@ -5194,4 +5441,33 @@
         verify(mMockedWwanDataServiceManager, never()).deactivateDataCall(anyInt(),
                 eq(DataService.REQUEST_REASON_NORMAL), any(Message.class));
     }
+
+    @Test
+    public void testRadioOffTearDown() throws Exception  {
+        testSetupDataNetwork();
+        doReturn(true).when(mSST).isPendingRadioPowerOffAfterDataOff();
+        mDataNetworkControllerUT.tearDownAllDataNetworks(
+                DataNetwork.TEAR_DOWN_REASON_AIRPLANE_MODE_ON);
+        processAllMessages();
+        verifyAllDataDisconnected();
+        verify(mMockedDataNetworkControllerCallback).onAnyDataNetworkExistingChanged(eq(false));
+
+        clearInvocations(mMockedDataNetworkControllerCallback);
+        mDataNetworkControllerUT.addNetworkRequest(
+                createNetworkRequest(NetworkCapabilities.NET_CAPABILITY_INTERNET));
+        processAllMessages();
+        verifyAllDataDisconnected();
+        verify(mMockedDataNetworkControllerCallback, never()).onAnyDataNetworkExistingChanged(
+                anyBoolean());
+    }
+
+    @Test
+    public void testGetInternetEvaluation() throws Exception {
+        testSetupDataNetwork();
+        doReturn(true).when(mSST).isPendingRadioPowerOffAfterDataOff();
+        assertThat(mDataNetworkControllerUT.getInternetEvaluation(false/*ignoreExistingNetworks*/)
+                .containsDisallowedReasons()).isFalse();
+        assertThat(mDataNetworkControllerUT.getInternetEvaluation(true/*ignoreExistingNetworks*/)
+                .containsDisallowedReasons()).isTrue();
+    }
 }
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 ebfc41a..7795e42 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
@@ -53,6 +53,7 @@
 import android.telephony.AccessNetworkConstants.TransportType;
 import android.telephony.Annotation;
 import android.telephony.Annotation.DataFailureCause;
+import android.telephony.CarrierConfigManager;
 import android.telephony.DataFailCause;
 import android.telephony.DataSpecificRegistrationInfo;
 import android.telephony.LteVopsSupportInfo;
@@ -256,12 +257,18 @@
 
     private void setSuccessfulSetupDataResponse(DataServiceManager dsm, int cid,
             List<TrafficDescriptor> tds, Qos defaultQos) {
+        setSuccessfulSetupDataResponse(dsm, cid, tds, defaultQos,
+                PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED);
+    }
+
+    private void setSuccessfulSetupDataResponse(DataServiceManager dsm, int cid,
+            List<TrafficDescriptor> tds, Qos defaultQos, int netwokrValidationResult) {
         doAnswer(invocation -> {
             final Message msg = (Message) invocation.getArguments()[10];
 
             DataCallResponse response = createDataCallResponse(
                     cid, DataCallResponse.LINK_STATUS_ACTIVE, tds, defaultQos,
-                    PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED);
+                    netwokrValidationResult);
             msg.getData().putParcelable("data_call_response", response);
             msg.arg1 = DataServiceCallback.RESULT_SUCCESS;
             msg.sendToTarget();
@@ -320,39 +327,47 @@
     }
 
     private void serviceStateChanged(@Annotation.NetworkType int networkType,
-            @NetworkRegistrationInfo.RegistrationState int regState) {
-        serviceStateChanged(networkType, regState, null);
+            @NetworkRegistrationInfo.RegistrationState int regState, boolean isNtn) {
+        serviceStateChanged(networkType, regState, null, isNtn);
     }
 
     private void serviceStateChanged(@Annotation.NetworkType int networkType,
             @NetworkRegistrationInfo.RegistrationState int regState,
-            DataSpecificRegistrationInfo dsri) {
-        ServiceState ss = new ServiceState();
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+            DataSpecificRegistrationInfo dsri, boolean isNtn) {
+        doReturn(isNtn).when(mServiceState).isUsingNonTerrestrialNetwork();
+
+        doReturn(new NetworkRegistrationInfo.Builder()
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                 .setAccessNetworkTechnology(networkType)
                 .setRegistrationState(regState)
                 .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
                 .setDataSpecificInfo(dsri)
-                .build());
+                .setIsNonTerrestrialNetwork(isNtn)
+                .build()).when(mServiceState)
+                .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
 
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+        doReturn(new NetworkRegistrationInfo.Builder()
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WLAN)
                 .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_IWLAN)
                 .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
                 .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
-                .build());
+                .build()).when(mServiceState)
+                .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
 
-        ss.addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+        doReturn(new NetworkRegistrationInfo.Builder()
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                 .setAccessNetworkTechnology(networkType)
                 .setRegistrationState(regState)
                 .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
-                .build());
-        ss.setDataRoamingFromRegistration(regState
-                == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
-        doReturn(ss).when(mSST).getServiceState();
-        doReturn(ss).when(mPhone).getServiceState();
+                .build()).when(mServiceState)
+                .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_CS,
+                        AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        boolean isRoaming = regState == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
+        doReturn(isRoaming).when(mServiceState).getDataRoamingFromRegistration();
+        doReturn(isRoaming).when(mServiceState).getDataRoaming();
 
         if (mDataNetworkUT != null) {
             mDataNetworkUT.obtainMessage(9/*EVENT_SERVICE_STATE_CHANGED*/).sendToTarget();
@@ -410,9 +425,16 @@
         doReturn(FAKE_IMSI).when(mPhone).getSubscriberId();
         doReturn(true).when(mDataNetworkController)
                 .isNetworkRequestExisting(any(TelephonyNetworkRequest.class));
+        doReturn(Set.of(NetworkCapabilities.NET_CAPABILITY_IMS,
+                NetworkCapabilities.NET_CAPABILITY_EIMS, NetworkCapabilities.NET_CAPABILITY_XCAP))
+                .when(mDataConfigManager).getForcedCellularTransportCapabilities();
+        doReturn(CarrierConfigManager.SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED)
+                .when(mDataConfigManager).getSatelliteDataSupportMode();
+        doReturn(true).when(mFeatureFlags).carrierEnabledSatelliteFlag();
+        doReturn(true).when(mFeatureFlags).satelliteInternet();
 
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
-                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, false/*isNtn*/);
     }
 
     @After
@@ -446,7 +468,7 @@
         NetworkRequestList networkRequestList = new NetworkRequestList();
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
 
         setSuccessfulSetupDataResponse(
                 mMockedWwanDataServiceManager, 123, Collections.emptyList(), mDefaultQos);
@@ -551,12 +573,13 @@
                 .build();
         // Out of service
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
-                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, dsri);
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, dsri,
+                false/*isNtn*/);
 
         NetworkRequestList networkRequestList = new NetworkRequestList();
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
 
         setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
 
@@ -597,7 +620,8 @@
                 .build();
         // Out of service
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
-                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, dsri);
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING, dsri,
+                false/*isNtn*/);
 
         DataCallResponse response = new DataCallResponse.Builder()
                 .setCause(0)
@@ -646,6 +670,19 @@
         // The final network should not have NOT_SUSPENDED because the device is OOS.
         assertThat(mDataNetworkUT.getNetworkCapabilities().hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)).isFalse();
+
+        // Verify recreation triggers notifyDataConnection with new network agent Id.
+        ArgumentCaptor<PreciseDataConnectionState> pdcsCaptor =
+                ArgumentCaptor.forClass(PreciseDataConnectionState.class);
+
+        // 4 times connecting, connected, data state changed, re-create network agent
+        verify(mPhone, times(4)).notifyDataConnection(pdcsCaptor.capture());
+        List<PreciseDataConnectionState> pdcsList = pdcsCaptor.getAllValues();
+        assertThat(pdcsList.get(0).getState()).isEqualTo(TelephonyManager.DATA_CONNECTING);
+        assertThat(pdcsList.get(1).getState()).isEqualTo(TelephonyManager.DATA_CONNECTED);
+        assertThat(pdcsList.get(2).getState()).isEqualTo(TelephonyManager.DATA_SUSPENDED);
+        assertThat(pdcsList.get(3).getNetId())
+                .isNotEqualTo(pdcsList.get(2).getNetId());
     }
 
     @Test
@@ -653,7 +690,7 @@
         NetworkRequestList networkRequestList = new NetworkRequestList();
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
         setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
 
         mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
@@ -742,7 +779,7 @@
         NetworkRequestList networkRequestList = new NetworkRequestList();
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
 
         List<TrafficDescriptor> tds = List.of(
                 new TrafficDescriptor(null, new TrafficDescriptor.OsAppId(
@@ -777,7 +814,7 @@
         NetworkRequestList networkRequestList = new NetworkRequestList();
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
 
         List<TrafficDescriptor> tds = List.of(
                 new TrafficDescriptor(null, new TrafficDescriptor.OsAppId(
@@ -810,7 +847,7 @@
         NetworkRequestList networkRequestList = new NetworkRequestList();
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
 
         List<TrafficDescriptor> tds = List.of(
                 new TrafficDescriptor(null, new TrafficDescriptor.OsAppId(
@@ -843,7 +880,7 @@
         NetworkRequestList networkRequestList = new NetworkRequestList();
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
 
         List<TrafficDescriptor> tds = List.of(
                 new TrafficDescriptor(null, new TrafficDescriptor.OsAppId(
@@ -877,7 +914,7 @@
         NetworkRequestList networkRequestList = new NetworkRequestList();
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
 
         List<TrafficDescriptor> tds = List.of(
                 new TrafficDescriptor(null, new TrafficDescriptor.OsAppId(
@@ -962,7 +999,7 @@
         NetworkRequestList networkRequestList = new NetworkRequestList();
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
 
         setSuccessfulSetupDataResponse(mMockedWlanDataServiceManager, 123);
 
@@ -1183,7 +1220,7 @@
         TelephonyNetworkRequest networkRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
-                .build(), mPhone);
+                .build(), mPhone, mFeatureFlags);
         networkRequestList.add(networkRequest);
 
         SimulatedCommands simulatedCommands2 = mock(SimulatedCommands.class);
@@ -1225,6 +1262,29 @@
     }
 
     @Test
+    public void testNetworkRequestDetachedBeforeConnected() throws Exception {
+        doReturn(true).when(mFeatureFlags).keepEmptyRequestsNetwork();
+        NetworkRequestList networkRequestList = new NetworkRequestList(new TelephonyNetworkRequest(
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
+                        .build(), mPhone, mFeatureFlags));
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mImsDataProfile, networkRequestList,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN, DataAllowedReason.NORMAL,
+                mDataNetworkCallback);
+        replaceInstance(DataNetwork.class, "mDataCallSessionStats",
+                mDataNetworkUT, mDataCallSessionStats);
+
+        // Remove the request before the network connect.
+        mDataNetworkUT.detachNetworkRequest(networkRequestList.getFirst(), false/*should retry*/);
+        setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
+        processAllMessages();
+
+        // Verify the request proceed to connected state even without requests.
+        assertThat(mDataNetworkUT.isConnected()).isTrue();
+    }
+
+    @Test
     public void testAdminAndOwnerUids() throws Exception {
         doReturn(ADMIN_UID2).when(mCarrierPrivilegesTracker).getCarrierServicePackageUid();
         setupDataNetwork();
@@ -1238,7 +1298,7 @@
         NetworkRequestList networkRequestList = new NetworkRequestList();
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
 
         setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
 
@@ -1335,7 +1395,7 @@
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
 
         // Data disabled
         doReturn(false).when(mDataSettingsManager).isDataEnabled();
@@ -1371,14 +1431,14 @@
     @Test
     public void testRestrictedNetworkDataRoamingEnabled() throws Exception {
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
-                NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
+                NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING, false/*isNtn*/);
 
         NetworkRequestList networkRequestList = new NetworkRequestList();
         // Restricted network request
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
 
         // Data roaming disabled
         doReturn(false).when(mDataSettingsManager).isDataRoamingEnabled();
@@ -1516,7 +1576,7 @@
         NetworkRequestList networkRequestList = new NetworkRequestList();
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
         mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
                 mDataServiceManagers, mInternetDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
@@ -1620,7 +1680,7 @@
                         LteVopsSupportInfo.LTE_STATUS_SUPPORTED))
                 .build();
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
-                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri, false/*isNtn*/);
 
         assertThat(mDataNetworkUT.getNetworkCapabilities().hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_MMTEL)).isTrue();
@@ -1634,7 +1694,7 @@
                 .build();
         logd("Trigger non VoPS");
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
-                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri, false/*isNtn*/);
         // MMTEL should not be removed.
         assertThat(mDataNetworkUT.getNetworkCapabilities().hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_MMTEL)).isTrue();
@@ -1651,7 +1711,7 @@
                         LteVopsSupportInfo.LTE_STATUS_SUPPORTED))
                 .build();
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
-                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri, false/*isNtn*/);
 
         assertThat(mDataNetworkUT.getNetworkCapabilities().hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_MMTEL)).isTrue();
@@ -1665,7 +1725,7 @@
                 .build();
         logd("Trigger non VoPS");
         serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
-                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri);
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, dsri, false/*isNtn*/);
         // MMTEL should be removed to reflect the actual Vops status.
         assertThat(mDataNetworkUT.getNetworkCapabilities().hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_MMTEL)).isFalse();
@@ -1724,7 +1784,7 @@
 
         TelephonyNetworkRequest networkRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder().addCapability(
-                        NetworkCapabilities.NET_CAPABILITY_SUPL).build(), mPhone);
+                        NetworkCapabilities.NET_CAPABILITY_SUPL).build(), mPhone, mFeatureFlags);
         mDataNetworkUT.attachNetworkRequests(new NetworkRequestList(networkRequest));
         processAllMessages();
 
@@ -1747,7 +1807,7 @@
 
         TelephonyNetworkRequest networkRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder().addCapability(
-                        NetworkCapabilities.NET_CAPABILITY_SUPL).build(), mPhone);
+                        NetworkCapabilities.NET_CAPABILITY_SUPL).build(), mPhone, mFeatureFlags);
         mDataNetworkUT.attachNetworkRequests(new NetworkRequestList(networkRequest));
         processAllMessages();
 
@@ -1900,7 +1960,7 @@
         NetworkRequestList networkRequestList = new NetworkRequestList();
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
 
         mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
                 mDataServiceManagers, mInternetDataProfile, networkRequestList,
@@ -2044,7 +2104,7 @@
         NetworkRequestList networkRequestList = new NetworkRequestList();
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
         mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
                 mDataServiceManagers, m5gDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, DataAllowedReason.NORMAL,
@@ -2141,7 +2201,7 @@
         NetworkRequest.Builder builder = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
         builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL);
-        networkRequestList.add(new TelephonyNetworkRequest(builder.build(), mPhone));
+        networkRequestList.add(new TelephonyNetworkRequest(builder.build(), mPhone, mFeatureFlags));
         mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
                 mDataServiceManagers, mImsDataProfile, networkRequestList,
                 AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
@@ -2237,6 +2297,51 @@
                 .isEqualTo(PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED);
     }
 
+    @Test
+    public void testHandoverWithSuccessNetworkValidation_FlagEnabled() throws Exception {
+        when(mFeatureFlags.networkValidation()).thenReturn(true);
+
+        setupDataNetwork();
+
+        setSuccessfulSetupDataResponse(
+                mMockedWlanDataServiceManager, 456, Collections.emptyList(), null,
+                PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS);
+        TelephonyNetworkAgent mockNetworkAgent = Mockito.mock(TelephonyNetworkAgent.class);
+        replaceInstance(DataNetwork.class, "mNetworkAgent",
+                mDataNetworkUT, mockNetworkAgent);
+        // Now handover to IWLAN
+        mDataNetworkUT.startHandover(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, null);
+        processAllMessages();
+
+        verify(mMockedWwanDataServiceManager).startHandover(eq(123), any(Message.class));
+        verify(mLinkBandwidthEstimator).unregisterCallback(any(
+                LinkBandwidthEstimatorCallback.class));
+        assertThat(mDataNetworkUT.getTransport())
+                .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+        assertThat(mDataNetworkUT.getId()).isEqualTo(456);
+        verify(mDataNetworkCallback).onHandoverSucceeded(eq(mDataNetworkUT));
+
+        ArgumentCaptor<PreciseDataConnectionState> pdcsCaptor =
+                ArgumentCaptor.forClass(PreciseDataConnectionState.class);
+        verify(mPhone, times(4)).notifyDataConnection(pdcsCaptor.capture());
+        List<PreciseDataConnectionState> pdcsList = pdcsCaptor.getAllValues();
+
+        assertThat(pdcsList).hasSize(4);
+        assertThat(pdcsList.get(0).getState()).isEqualTo(TelephonyManager.DATA_CONNECTING);
+        assertThat(pdcsList.get(1).getState()).isEqualTo(TelephonyManager.DATA_CONNECTED);
+        assertThat(pdcsList.get(1).getNetworkValidationStatus())
+                .isEqualTo(PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED);
+        assertThat(pdcsList.get(2).getState())
+                .isEqualTo(TelephonyManager.DATA_HANDOVER_IN_PROGRESS);
+        assertThat(pdcsList.get(2).getNetworkValidationStatus())
+                .isEqualTo(PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED);
+        assertThat(pdcsList.get(3).getState()).isEqualTo(TelephonyManager.DATA_CONNECTED);
+        assertThat(pdcsList.get(3).getNetworkValidationStatus())
+                .isEqualTo(PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS);
+
+        verify(mDataNetworkCallback).onHandoverSucceeded(eq(mDataNetworkUT));
+    }
+
     private void setupIwlanDataNetwork() throws Exception {
         // setup iwlan data network over ims
         doReturn(mIwlanNetworkRegistrationInfo).when(mServiceState).getNetworkRegistrationInfo(
@@ -2248,7 +2353,7 @@
         NetworkRequestList networkRequestList = new NetworkRequestList();
         networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
-                .build(), mPhone));
+                .build(), mPhone, mFeatureFlags));
         setSuccessfulSetupDataResponse(mMockedWlanDataServiceManager, 123);
         mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
                 mDataServiceManagers, mImsDataProfile, networkRequestList,
@@ -2264,7 +2369,7 @@
         NetworkRequest.Builder builder = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS);
         if (isMmtel) builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMTEL);
-        networkRequestList.add(new TelephonyNetworkRequest(builder.build(), mPhone));
+        networkRequestList.add(new TelephonyNetworkRequest(builder.build(), mPhone, mFeatureFlags));
 
         setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
 
@@ -2277,6 +2382,40 @@
         processAllMessages();
     }
 
+    private void setupNonTerrestrialDataNetwork() {
+        doReturn(true).when(mServiceState).isUsingNonTerrestrialNetwork();
+        NetworkRequestList networkRequestList = new NetworkRequestList();
+
+        networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                .build(), mPhone, mFeatureFlags));
+        setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
+
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mInternetDataProfile, networkRequestList,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                DataAllowedReason.NORMAL, mDataNetworkCallback);
+        processAllMessages();
+
+        assertThat(mDataNetworkUT.isSatellite()).isTrue();
+    }
+
+    private void setupTerrestrialDataNetwork() {
+        NetworkRequestList networkRequestList = new NetworkRequestList();
+        networkRequestList.add(new TelephonyNetworkRequest(new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .build(), mPhone, mFeatureFlags));
+        setSuccessfulSetupDataResponse(mMockedWwanDataServiceManager, 123);
+
+        mDataNetworkUT = new DataNetwork(mPhone, mFeatureFlags, Looper.myLooper(),
+                mDataServiceManagers, mInternetDataProfile, networkRequestList,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+                DataAllowedReason.NORMAL, mDataNetworkCallback);
+        processAllMessages();
+    }
+
     @Test
     public void testMmsCapabilityRemovedWhenMmsPreferredOnIwlan() throws Exception {
         doReturn(true).when(mFeatureFlags).forceIwlanMms();
@@ -2293,6 +2432,32 @@
         // Now QNS prefers MMS on IWLAN
         doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WLAN).when(mAccessNetworksManager)
                 .getPreferredTransportByNetworkCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
+        // Verify an mms apn that shares the same apn name doesn't count as an alternative.
+        ApnSetting mmsApnWithSameApn = new ApnSetting.Builder()
+                .setId(2164)
+                .setOperatorNumeric("12345")
+                .setEntryName("fake_mms_apn")
+                .setApnName("fake_apn")
+                .setApnTypeBitmask(ApnSetting.TYPE_MMS)
+                .setProtocol(ApnSetting.PROTOCOL_IPV6)
+                .setRoamingProtocol(ApnSetting.PROTOCOL_IP)
+                .setCarrierEnabled(true)
+                .setNetworkTypeBitmask((int) TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN)
+                .build();
+        doReturn(new DataProfile.Builder().setApnSetting(mmsApnWithSameApn)
+                .setTrafficDescriptor(new TrafficDescriptor("fake_apn", null))
+                .build()).when(mDataProfileManager).getDataProfileForNetworkRequest(
+                any(TelephonyNetworkRequest.class),
+                eq(TelephonyManager.NETWORK_TYPE_IWLAN), eq(false), eq(false), eq(false));
+        accessNetworksManagerCallbackArgumentCaptor.getValue()
+                .onPreferredTransportChanged(NetworkCapabilities.NET_CAPABILITY_MMS, false);
+        processAllMessages();
+
+        // Check if MMS capability remains intact.
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)).isTrue();
+
+        // Verify MMS capability is removed if using a valid MMS alternative APN.
         doReturn(mMmsDataProfile).when(mDataProfileManager).getDataProfileForNetworkRequest(
                 any(TelephonyNetworkRequest.class),
                     eq(TelephonyManager.NETWORK_TYPE_IWLAN), eq(false), eq(false), eq(false));
@@ -2355,4 +2520,140 @@
 
         verify(mDataNetworkCallback).onQosSessionsChanged(newQosSessions);
     }
+
+    @Test
+    public void testUnrestrictedSatelliteNetworkCapabilities() {
+        setupNonTerrestrialDataNetwork();
+        assertThat(mDataNetworkUT.isSatellite()).isTrue();
+
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)).isFalse();
+
+        // Test constrained traffic
+        doReturn(CarrierConfigManager.SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED)
+                .when(mDataConfigManager).getSatelliteDataSupportMode();
+        mDataNetworkUT.sendMessage(22/*EVENT_VOICE_CALL_STARTED*/); // update network capabilities
+        processAllMessages();
+
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
+        try {
+            assertThat(mDataNetworkUT.getNetworkCapabilities()
+                    .hasCapability(DataUtils.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)).isFalse();
+        } catch (Exception ignored) { }
+
+        // Test not constrained traffic
+        doReturn(CarrierConfigManager.SATELLITE_DATA_SUPPORT_ALL)
+                .when(mDataConfigManager).getSatelliteDataSupportMode();
+        mDataNetworkUT.sendMessage(22/*EVENT_VOICE_CALL_STARTED*/); // update network capabilities
+        processAllMessages();
+
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
+        // TODO(enable after NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED become a default cap)
+//        try {
+//            assertThat(mDataNetworkUT.getNetworkCapabilities()
+//                    .hasCapability(DataUtils.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)).isTrue();
+//        } catch (Exception ignored) {}
+    }
+
+    @Test
+    public void testIsTransportSatelliteSupportNonImsNonTerrestrialNetwork() throws Exception {
+        // Service state at Non-terrestrial network
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, true/*isNtn*/);
+
+        // set up data network with transport type satellite + Internet
+        setupNonTerrestrialDataNetwork();
+
+        //Check now transport type for the data network is satellite
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasTransport(NetworkCapabilities.TRANSPORT_SATELLITE)).isTrue();
+    }
+
+    @Test
+    public void testIsTransportSatelliteSupportWithImsNonTerrestrialNetwork() throws Exception {
+        // Service state at Non-terrestrial network
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, true/*isNtn*/);
+
+        // set up data network with transport type satellite + IMS
+        createImsDataNetwork(false/*isMmtel*/);
+
+        //Check transport type for the data network is Cellular for Ims at non-terrestrial network
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).isTrue();
+    }
+
+    @Test
+    public void testSatelliteTransportSupportedNonImsTerrestrialToNonTerrestrial()
+            throws Exception {
+        // Service state at terrestrial network
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, false/*isNtn*/);
+
+        // set up data network with transport type cellular + Internet
+        setupTerrestrialDataNetwork();
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).isTrue();
+
+        // Service State change terrestrial to non-terrestrial
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, true/*isNtn*/);
+
+        // Make sure transport type for the data network is still Cellular
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).isTrue();
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasTransport(NetworkCapabilities.TRANSPORT_SATELLITE)).isFalse();
+
+        // Disconnect the Data call
+        mDataNetworkUT.sendMessage(19/*EVENT_DEACTIVATE_DATA_NETWORK_RESPONSE*/, 0/*Success*/);
+        processAllMessages();
+
+        // set up data network with transport type satellite + Internet
+        setupNonTerrestrialDataNetwork();
+
+        //Check now transport type for the data network is satellite
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasTransport(NetworkCapabilities.TRANSPORT_SATELLITE)).isTrue();
+    }
+
+    @Test
+    public void testSatelliteTransportSupportedNonImsNonTerrestrialToTerrestrial()
+            throws Exception {
+        // Service state at non-terrestrial network
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, true/*isNtn*/);
+
+        // set up data network with transport type satellite + Internet
+        setupNonTerrestrialDataNetwork();
+
+        //Check now transport type for the data network is satellite
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasTransport(NetworkCapabilities.TRANSPORT_SATELLITE)).isTrue();
+
+        // Service State change non-terrestrial to terrestrial
+        serviceStateChanged(TelephonyManager.NETWORK_TYPE_LTE,
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME, false/*isNtn*/);
+
+        // Make sure transport type for the data network is still satellite
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasTransport(NetworkCapabilities.TRANSPORT_SATELLITE)).isTrue();
+
+        // Make sure transport type for the data network is not cellular
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).isFalse();
+
+        // Disconnect the Data call
+        mDataNetworkUT.sendMessage(19/*EVENT_DEACTIVATE_DATA_NETWORK_RESPONSE*/, 0/*Success*/);
+        processAllMessages();
+
+        // set up data network with transport type cellular + Internet
+        setupTerrestrialDataNetwork();
+
+        //Check now transport type for the data network is cellular
+        assertThat(mDataNetworkUT.getNetworkCapabilities()
+                .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)).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 44d207d..30ce46f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
@@ -888,7 +888,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataProfile dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
                 TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
 
@@ -899,7 +899,7 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)
                 .build();
-        tnr = new TelephonyNetworkRequest(request, mPhone);
+        tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
                 TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
 
@@ -909,7 +909,7 @@
         request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
                 .build();
-        tnr = new TelephonyNetworkRequest(request, mPhone);
+        tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
                 TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dp.canSatisfy(tnr.getCapabilities())).isTrue();
@@ -918,7 +918,7 @@
         request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
                 .build();
-        tnr = new TelephonyNetworkRequest(request, mPhone);
+        tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
                 TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dp).isNull();
@@ -931,7 +931,7 @@
         request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
                 .build();
-        tnr = new TelephonyNetworkRequest(request, mPhone);
+        tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
                 TelephonyManager.NETWORK_TYPE_NR, false, false , false);
         assertThat(dp.canSatisfy(tnr.getCapabilities())).isTrue();
@@ -943,7 +943,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataProfile dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
                 TelephonyManager.NETWORK_TYPE_GSM, false, false, false);
         // Should not find data profile due to RAT incompatible.
@@ -955,7 +955,7 @@
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN);
@@ -973,7 +973,7 @@
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting()).isNull();
@@ -986,7 +986,7 @@
         tnr = new TelephonyNetworkRequest(new NetworkRequest(new NetworkCapabilities()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
                 .addEnterpriseId(2), ConnectivityManager.TYPE_NONE,
-                0, NetworkRequest.Type.REQUEST), mPhone);
+                0, NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags);
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting()).isNull();
@@ -1002,7 +1002,7 @@
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting()).isNull();
@@ -1018,7 +1018,7 @@
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting()).isNull();
@@ -1036,7 +1036,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_RCS)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataProfile dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
                 TelephonyManager.NETWORK_TYPE_LTE, true, false, false);
 
@@ -1050,7 +1050,7 @@
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN);
@@ -1090,7 +1090,7 @@
         TelephonyNetworkRequest dunTnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_DUN)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         DataProfile dunDataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 dunTnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         DataNetwork dunInternetNetwork = Mockito.mock(DataNetwork.class);
@@ -1119,6 +1119,97 @@
     }
 
     @Test
+    public void testSetPreferredDataProfileBySubscriptionId() {
+        // NetworkRequest.
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        .build(), mPhone, mFeatureFlags);
+        // DataNetwork for internet
+        DataNetwork internetNetwork = Mockito.mock(DataNetwork.class);
+
+        // Step 1. With sudId 1, connect to internet and set as prefer APN.
+        // Test for SubId 1.
+        doReturn(1).when(mPhone).getSubId();
+
+        // Sim Loaded and loads profiles.
+        changeSimStateTo(TelephonyManager.SIM_STATE_LOADED);
+        mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
+        processAllMessages();
+
+        // Find DataProfile based on network request
+        DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
+        assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN);
+        dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime());
+
+        // Connected to internet. At this time, This test expects that dataProfile is stored in
+        // mLastInternetDataProfiles for subid 1.
+        doReturn(dataProfile).when(internetNetwork).getDataProfile();
+        doReturn(new DataNetworkController.NetworkRequestList(List.of(tnr)))
+                .when(internetNetwork).getAttachedNetworkRequestList();
+        mDataNetworkControllerCallback.onConnectedInternetDataNetworksChanged(
+                Set.of(internetNetwork));
+        processAllMessages();
+
+        // After internet connected, preferred APN should be set
+        assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isTrue();
+
+
+        // Step 2. test prefer apn for subId 2.
+        // Test for SubId 2.
+        doReturn(2).when(mPhone).getSubId();
+        // Sim Loaded and loads profiles.
+        changeSimStateTo(TelephonyManager.SIM_STATE_ABSENT);
+        changeSimStateTo(TelephonyManager.SIM_STATE_LOADED);
+        mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
+        processAllMessages();
+
+        // Find DataProfile based on network request
+        dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                tnr, TelephonyManager.NETWORK_TYPE_CDMA, false, false, false);
+        assertThat(dataProfile.getApnSetting().getApnName())
+                .isEqualTo(GENERAL_PURPOSE_APN_LEGACY_RAT);
+        dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime());
+        doReturn(dataProfile).when(internetNetwork).getDataProfile();
+
+        // APN reset
+        mPreferredApnId = -1;
+        mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
+        processAllMessages();
+
+        // Since a new subid has been loaded, preferred APN should be null regardless of the last
+        // internet connection of previous subid.
+        assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isFalse();
+
+
+        // Step 3. try again back to subId 1.
+        // Test for SubId 1.
+        doReturn(1).when(mPhone).getSubId();
+        // Sim Loaded and loads profiles.
+        changeSimStateTo(TelephonyManager.SIM_STATE_ABSENT);
+        changeSimStateTo(TelephonyManager.SIM_STATE_LOADED);
+        mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
+        processAllMessages();
+
+        // Find DataProfile based on network request
+        dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
+                tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
+        assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN);
+        dataProfile.setLastSetupTimestamp(SystemClock.elapsedRealtime());
+        doReturn(dataProfile).when(internetNetwork).getDataProfile();
+
+        // APN reset
+        mPreferredApnId = -1;
+        mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
+        processAllMessages();
+
+        // Since the old subid has been loaded, Even if preferapn for subid 1 is not in the db, the
+        // preferapn can be loaded by mLastInternetDataProfiles
+        assertThat(mDataProfileManagerUT.isDataProfilePreferred(dataProfile)).isTrue();
+    }
+
+    @Test
     public void testSetInitialAttachDataProfile() {
         ArgumentCaptor<DataProfile> dataProfileCaptor =
                 ArgumentCaptor.forClass(DataProfile.class);
@@ -1189,7 +1280,7 @@
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile).isNull();
@@ -1198,7 +1289,7 @@
         tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo("sos");
@@ -1207,7 +1298,7 @@
         tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile).isEqualTo(null);
@@ -1237,7 +1328,7 @@
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile).isNull();
@@ -1246,7 +1337,7 @@
         tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo("sos");
@@ -1255,7 +1346,7 @@
         tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile).isEqualTo(null);
@@ -1291,7 +1382,7 @@
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 new TelephonyNetworkRequest(new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
-                        .build(), mPhone),
+                        .build(), mPhone, mFeatureFlags),
                 TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(IMS_APN);
     }
@@ -1301,7 +1392,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         // This should get the merged data profile after deduping.
         DataProfile dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
                 TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
@@ -1439,7 +1530,7 @@
     public void testDefaultEmergencyDataProfileValid() {
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)
-                .build(), mPhone);
+                .build(), mPhone, mFeatureFlags);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
 
@@ -1456,7 +1547,7 @@
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(GENERAL_PURPOSE_APN);
@@ -1522,7 +1613,7 @@
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         changeSimStateTo(TelephonyManager.SIM_STATE_LOADED);
         mDataProfileManagerUT.obtainMessage(2 /*EVENT_APN_DATABASE_CHANGED*/).sendToTarget();
         processAllMessages();
@@ -1828,7 +1919,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataProfile dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
                 TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
 
@@ -1852,7 +1943,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataProfile dp = mDataProfileManagerUT.getDataProfileForNetworkRequest(tnr,
                 TelephonyManager.NETWORK_TYPE_LTE, false, false, false);
 
@@ -1883,7 +1974,7 @@
 
         // Verify the we can get the previously permanent failed data profile again.
         assertThat(mDataProfileManagerUT.getDataProfileForNetworkRequest(
-                new TelephonyNetworkRequest(request, mPhone),
+                new TelephonyNetworkRequest(request, mPhone, mFeatureFlags),
                 TelephonyManager.NETWORK_TYPE_LTE, false, false, false))
                 .isNotNull();
     }
@@ -1902,7 +1993,7 @@
         // flag is enabled at data profile, during esim bootstrap provisioning
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build(), mPhone);
+                .build(), mPhone, mFeatureFlags);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, true, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(
@@ -1912,7 +2003,7 @@
         // is enabled at data profile, during esim bootstrap provisioning
         tnr = new TelephonyNetworkRequest(new NetworkRequest.Builder()
                  .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
-                 .build(), mPhone);
+                 .build(), mPhone, mFeatureFlags);
         dataProfile  = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, true, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo("IMS_APN");
@@ -1921,7 +2012,7 @@
         // is disabled at data profile, during esim bootstrap provisioning
         tnr = new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
-                .build(), mPhone);
+                .build(), mPhone, mFeatureFlags);
         dataProfile  = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, true, false);
         assertThat(dataProfile).isEqualTo(null);
@@ -1930,7 +2021,7 @@
         // is disabled at data profile, during esim bootstrap provisioning
         tnr = new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_RCS)
-                .build(), mPhone);
+                .build(), mPhone, mFeatureFlags);
         dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, true, false);
         assertThat(dataProfile).isEqualTo(null);
@@ -1950,7 +2041,7 @@
         // type
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build(), mPhone);
+                .build(), mPhone, mFeatureFlags);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, false, true, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(
@@ -1972,7 +2063,7 @@
         // type
         TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_RCS)
-                .build(), mPhone);
+                .build(), mPhone, mFeatureFlags);
         DataProfile dataProfile = mDataProfileManagerUT.getDataProfileForNetworkRequest(
                 tnr, TelephonyManager.NETWORK_TYPE_LTE, true, true, false);
         assertThat(dataProfile.getApnSetting().getApnName()).isEqualTo(RCS_APN1);
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 2541bd1..acfa16d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
@@ -292,7 +292,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataNetworkController.NetworkRequestList
                 networkRequestList = new DataNetworkController.NetworkRequestList(tnr);
         mDataRetryManagerUT.evaluateDataSetupRetry(mDataProfile1,
@@ -316,7 +316,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataNetworkController.NetworkRequestList
                 networkRequestList = new DataNetworkController.NetworkRequestList(tnr);
         mDataRetryManagerUT.evaluateDataSetupRetry(mDataProfile3,
@@ -345,7 +345,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataNetworkController.NetworkRequestList
                 networkRequestList = new DataNetworkController.NetworkRequestList(tnr);
         mDataRetryManagerUT.evaluateDataSetupRetry(mDataProfile3,
@@ -432,7 +432,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataNetworkController.NetworkRequestList
                 networkRequestList = new DataNetworkController.NetworkRequestList(tnr);
 
@@ -507,17 +507,38 @@
 
         // Test: cancelPendingHandoverRetry
         DataNetwork mockDn = Mockito.mock(DataNetwork.class);
+        NetworkRequest request = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
+                .build();
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
+        DataNetworkController.NetworkRequestList
+                networkRequestList = new DataNetworkController.NetworkRequestList(tnr);
+        doReturn(networkRequestList).when(mockDn).getAttachedNetworkRequestList();
+        doReturn(AccessNetworkConstants.TRANSPORT_TYPE_WWAN).when(mockDn).getTransport();
+        doReturn(mDataProfile3).when(mockDn).getDataProfile();
         Field field = DataRetryManager.class.getDeclaredField("mDataRetryEntries");
         field.setAccessible(true);
         List<DataRetryEntry> mDataRetryEntries =
                 (List<DataRetryEntry>) field.get(mDataRetryManagerUT);
-        retry = new DataHandoverRetryEntry.Builder<>()
-                .setDataNetwork(mockDn)
-                .build();
-        mDataRetryEntries.add(retry);
-        mDataRetryManagerUT.cancelPendingHandoverRetry(mockDn);
+        mDataRetryManagerUT.evaluateDataHandoverRetry(mockDn, 123, 1000);
         processAllMessages();
+        mDataRetryManagerUT.cancelPendingHandoverRetry(mockDn);
+        Mockito.clearInvocations(mDataRetryManagerCallbackMock);
+        processAllMessages();
+        retry = mDataRetryEntries.get(mDataRetryEntries.size() - 1);
 
+        ArgumentCaptor<List<ThrottleStatus>> throttleStatusCaptor =
+                ArgumentCaptor.forClass(List.class);
+        verify(mDataRetryManagerCallbackMock).onThrottleStatusChanged(
+                throttleStatusCaptor.capture());
+        assertThat(throttleStatusCaptor.getValue()).hasSize(1);
+        ThrottleStatus throttleStatus = throttleStatusCaptor.getValue().get(0);
+        assertThat(throttleStatus.getApnType()).isEqualTo(ApnSetting.TYPE_IMS);
+        assertThat(throttleStatus.getRetryType())
+                .isEqualTo(ThrottleStatus.RETRY_TYPE_HANDOVER);
+        assertThat(throttleStatus.getThrottleExpiryTimeMillis()).isEqualTo(-1);
+        assertThat(throttleStatus.getTransportType())
+                .isEqualTo(AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
         assertThat(mDataRetryManagerUT.isAnyHandoverRetryScheduled(mockDn)).isFalse();
         assertThat(retry.getState()).isEqualTo(DataRetryEntry.RETRY_STATE_CANCELLED);
     }
@@ -535,7 +556,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataNetworkController.NetworkRequestList
                 networkRequestList = new DataNetworkController.NetworkRequestList(tnr);
         mDataRetryManagerUT.evaluateDataSetupRetry(mDataProfile1,
@@ -568,7 +589,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataNetworkController.NetworkRequestList
                 networkRequestList = new DataNetworkController.NetworkRequestList(tnr);
 
@@ -647,7 +668,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataNetworkController.NetworkRequestList
                 networkRequestList = new DataNetworkController.NetworkRequestList(tnr);
 
@@ -709,7 +730,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataNetworkController.NetworkRequestList
                 networkRequestList = new DataNetworkController.NetworkRequestList(tnr);
 
@@ -798,7 +819,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataNetworkController.NetworkRequestList
                 networkRequestList = new DataNetworkController.NetworkRequestList(tnr);
         mDataRetryManagerUT.evaluateDataSetupRetry(mDataProfile1,
@@ -839,7 +860,7 @@
         NetworkRequest request = new NetworkRequest.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
-        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone);
+        TelephonyNetworkRequest tnr = new TelephonyNetworkRequest(request, mPhone, mFeatureFlags);
         DataNetworkController.NetworkRequestList
                 networkRequestList = new DataNetworkController.NetworkRequestList(tnr);
 
@@ -853,7 +874,7 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
-                .build(), mPhone);
+                .build(), mPhone, mFeatureFlags);
         assertThat(mDataRetryManagerUT.isSimilarNetworkRequestRetryScheduled(tnr,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).isTrue();
         assertThat(mDataRetryManagerUT.isSimilarNetworkRequestRetryScheduled(tnr,
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/DataUtilsTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataUtilsTest.java
index ea8a7d7..38de618 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataUtilsTest.java
@@ -18,24 +18,32 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.doReturn;
+
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.data.DataNetworkController.NetworkRequestList;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mockito;
 
 import java.util.List;
 
 public class DataUtilsTest extends TelephonyTest {
 
+    private FeatureFlags mFeatureFlags;
+
     @Before
     public void setUp() throws Exception {
         logd("DataUtilsTest +Setup!");
         super.setUp(getClass().getSimpleName());
+        mFeatureFlags = Mockito.mock(FeatureFlags.class);
+        doReturn(true).when(mFeatureFlags).satelliteInternet();
         logd("DataUtilsTest -Setup!");
     }
 
@@ -57,10 +65,13 @@
                 NetworkCapabilities.NET_CAPABILITY_ENTERPRISE,
                 NetworkCapabilities.NET_CAPABILITY_ENTERPRISE,
                 NetworkCapabilities.NET_CAPABILITY_ENTERPRISE,
+                NetworkCapabilities.NET_CAPABILITY_IMS,
+                NetworkCapabilities.NET_CAPABILITY_IMS,
         };
 
         int requestId = 0;
         int enterpriseId = 1;
+        int transportType = NetworkCapabilities.TRANSPORT_CELLULAR;
         TelephonyNetworkRequest networkRequest;
         for (int netCap : netCaps) {
             if (netCap == NetworkCapabilities.NET_CAPABILITY_ENTERPRISE) {
@@ -69,24 +80,31 @@
                                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                                 .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
                                 .addEnterpriseId(enterpriseId).build(), -1, requestId++,
-                        NetworkRequest.Type.REQUEST), mPhone);
+                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags);
                 if (enterpriseId == 1) enterpriseId++;
+            } else if (netCap == NetworkCapabilities.NET_CAPABILITY_IMS) {
+                networkRequest = new TelephonyNetworkRequest(new NetworkRequest(
+                        new NetworkCapabilities.Builder()
+                                .addTransportType(transportType)
+                                .addCapability(netCap).build(), -1, requestId++,
+                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags);
+                transportType = NetworkCapabilities.TRANSPORT_SATELLITE;
             } else {
                 networkRequest = new TelephonyNetworkRequest(new NetworkRequest(
                         new NetworkCapabilities.Builder()
                                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                                 .addCapability(netCap).build(), -1, requestId++,
-                        NetworkRequest.Type.REQUEST), mPhone);
+                        NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags);
             }
             requestList.add(networkRequest);
         }
 
-        assertThat(requestList).hasSize(8);
+        assertThat(requestList).hasSize(10);
 
         List<NetworkRequestList> requestListList =
-                DataUtils.getGroupedNetworkRequestList(requestList);
+                DataUtils.getGroupedNetworkRequestList(requestList, mFeatureFlags);
 
-        assertThat(requestListList).hasSize(5);
+        assertThat(requestListList).hasSize(7);
         requestList = requestListList.get(0);
         assertThat(requestList).hasSize(1);
         assertThat(requestList.get(0).hasCapability(
@@ -100,26 +118,40 @@
                 NetworkCapabilities.NET_CAPABILITY_MMS)).isTrue();
 
         requestList = requestListList.get(2);
-        assertThat(requestList).hasSize(2);
+        assertThat(requestList).hasSize(1);
         assertThat(requestList.get(0).hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_INTERNET)).isTrue();
-        assertThat(requestList.get(1).hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_INTERNET)).isTrue();
+                NetworkCapabilities.NET_CAPABILITY_IMS)).isTrue();
+        assertThat(requestList.get(0).getNativeNetworkRequest().hasTransport(
+                NetworkCapabilities.TRANSPORT_CELLULAR)).isTrue();
 
         requestList = requestListList.get(3);
         assertThat(requestList).hasSize(1);
         assertThat(requestList.get(0).hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)).isTrue();
-        assertThat(requestList.get(0).getCapabilityDifferentiator() == 1).isTrue();
+                NetworkCapabilities.NET_CAPABILITY_IMS)).isTrue();
+        assertThat(requestList.get(0).getNativeNetworkRequest().hasTransport(
+                NetworkCapabilities.TRANSPORT_SATELLITE)).isTrue();
 
         requestList = requestListList.get(4);
         assertThat(requestList).hasSize(2);
         assertThat(requestList.get(0).hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_INTERNET)).isTrue();
+        assertThat(requestList.get(1).hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_INTERNET)).isTrue();
+
+        requestList = requestListList.get(5);
+        assertThat(requestList).hasSize(1);
+        assertThat(requestList.get(0).hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)).isTrue();
-        assertThat(requestList.get(0).getCapabilityDifferentiator() == 2).isTrue();
+        assertThat(requestList.get(0).getCapabilityDifferentiator()).isEqualTo(1);
+
+        requestList = requestListList.get(6);
+        assertThat(requestList).hasSize(2);
+        assertThat(requestList.get(0).hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)).isTrue();
+        assertThat(requestList.get(0).getCapabilityDifferentiator()).isEqualTo(2);
         assertThat(requestList.get(1).hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)).isTrue();
-        assertThat(requestList.get(1).getCapabilityDifferentiator() == 2).isTrue();
+        assertThat(requestList.get(1).getCapabilityDifferentiator()).isEqualTo(2);
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/LinkBandwidthEstimatorTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/LinkBandwidthEstimatorTest.java
index d41be7d..dc4a58a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/LinkBandwidthEstimatorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/LinkBandwidthEstimatorTest.java
@@ -43,6 +43,7 @@
 
 import android.net.NetworkCapabilities;
 import android.os.Handler;
+import android.os.Looper;
 import android.telephony.CellIdentityLte;
 import android.telephony.ModemActivityInfo;
 import android.telephony.NetworkRegistrationInfo;
@@ -116,7 +117,7 @@
         when(mPhone.getSubId()).thenReturn(1);
         when(mSignalStrength.getDbm()).thenReturn(-100);
         when(mSignalStrength.getLevel()).thenReturn(1);
-        mLBE = new LinkBandwidthEstimator(mPhone, mTelephonyFacade);
+        mLBE = new LinkBandwidthEstimator(mPhone, Looper.myLooper(), mTelephonyFacade);
         mLBE.obtainMessage(MSG_DEFAULT_NETWORK_CHANGED, mNetworkCapabilities).sendToTarget();
         mLBE.obtainMessage(MSG_SCREEN_STATE_CHANGED, false).sendToTarget();
         mLBE.obtainMessage(MSG_ACTIVE_PHONE_CHANGED, 1).sendToTarget();
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 e011a60..2a1fedb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/PhoneSwitcherTest.java
@@ -23,9 +23,12 @@
 import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_SUCCESS;
 import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_VALIDATION_FAILED;
 import static android.telephony.TelephonyManager.SIM_STATE_LOADED;
+import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_NOT_REGISTERED;
+import static android.telephony.ims.RegistrationManager.REGISTRATION_STATE_REGISTERED;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
 import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
+import static android.telephony.ims.stub.ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
 
 import static com.android.internal.telephony.data.AutoDataSwitchController.EVALUATION_REASON_VOICE_CALL_END;
 import static com.android.internal.telephony.data.PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS;
@@ -42,6 +45,7 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -73,6 +77,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.ims.ImsException;
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.CommandsInterface;
@@ -84,6 +89,8 @@
 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 org.junit.After;
@@ -126,6 +133,7 @@
     private ISetOpportunisticDataCallback mSetOpptDataCallback1;
     private ISetOpportunisticDataCallback mSetOpptDataCallback2;
     PhoneSwitcher.ImsRegTechProvider mMockImsRegTechProvider;
+    PhoneSwitcher.ImsRegisterCallback mMockImsRegisterCallback;
     private SubscriptionInfo mSubscriptionInfo;
     private ISub mMockedIsub;
     private AutoDataSwitchController mAutoDataSwitchController;
@@ -167,6 +175,7 @@
         mSetOpptDataCallback1 = mock(ISetOpportunisticDataCallback.class);
         mSetOpptDataCallback2 = mock(ISetOpportunisticDataCallback.class);
         mMockImsRegTechProvider = mock(PhoneSwitcher.ImsRegTechProvider.class);
+        mMockImsRegisterCallback = mock(PhoneSwitcher.ImsRegisterCallback.class);
         mSubscriptionInfo = mock(SubscriptionInfo.class);
         mMockedIsub = mock(ISub.class);
         mAutoDataSwitchController = mock(AutoDataSwitchController.class);
@@ -221,8 +230,8 @@
         NetworkRequest internetNetworkRequest = addInternetNetworkRequest(null, 50);
 
         assertFalse("phone active after request", mPhoneSwitcherUT
-                .shouldApplyNetworkRequest(
-                        new TelephonyNetworkRequest(internetNetworkRequest, mPhone), 0));
+                .shouldApplyNetworkRequest(new TelephonyNetworkRequest(
+                        internetNetworkRequest, mPhone, mFeatureFlags), 0));
 
         // not registered yet - shouldn't inc
         verify(mActivePhoneSwitchHandler, never()).sendMessageAtTime(any(), anyLong());
@@ -628,9 +637,9 @@
         // Phone 0 (sub 1) should be activated as it has default data sub.
         assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId());
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
 
         // Set sub 2 as preferred sub should make phone 1 activated and phone 0 deactivated.
         mPhoneSwitcherUT.trySetOpportunisticDataSubscription(2, false, null);
@@ -645,9 +654,9 @@
 
         // switch shouldn't occur due to the higher priority event
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
         assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId());
         assertEquals(2, mPhoneSwitcherUT.getAutoSelectedDataSubId());
 
@@ -657,9 +666,9 @@
         assertEquals(2, mPhoneSwitcherUT.getActiveDataSubId());
         assertEquals(2, mPhoneSwitcherUT.getAutoSelectedDataSubId());
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
     }
 
     @Test
@@ -751,13 +760,13 @@
         verify(mMockRadioConfig, never()).setPreferredDataModem(anyInt(), any());
         verify(mActivePhoneSwitchHandler, never()).sendMessageAtTime(any(), anyLong());
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(mmsRequest, mPhone), 0));
+                new TelephonyNetworkRequest(mmsRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(mmsRequest, mPhone), 1));
+                new TelephonyNetworkRequest(mmsRequest, mPhone, mFeatureFlags), 1));
 
         // Set sub 2 as preferred sub should make phone 1 preferredDataModem
         doReturn(new SubscriptionInfoInternal.Builder(mSubscriptionManagerService
@@ -773,13 +782,13 @@
         processAllMessages();
         verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(mmsRequest, mPhone), 0));
+                new TelephonyNetworkRequest(mmsRequest, mPhone, mFeatureFlags), 0));
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(mmsRequest, mPhone), 1));
+                new TelephonyNetworkRequest(mmsRequest, mPhone, mFeatureFlags), 1));
 
         clearInvocations(mMockRadioConfig);
         clearInvocations(mActivePhoneSwitchHandler);
@@ -796,13 +805,13 @@
         processAllMessages();
         verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(mmsRequest, mPhone), 0));
+                new TelephonyNetworkRequest(mmsRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(mmsRequest, mPhone), 1));
+                new TelephonyNetworkRequest(mmsRequest, mPhone, mFeatureFlags), 1));
 
         // SetDataAllowed should never be triggered.
         verify(mCommandsInterface0, never()).setDataAllowed(anyBoolean(), any());
@@ -871,6 +880,11 @@
         mPhoneSwitcherUT.mImsRegTechProvider = mMockImsRegTechProvider;
     }
 
+    private void mockImsRegisterCallback(int phoneId) throws ImsException {
+        doNothing().when(mMockImsRegisterCallback).setCallback(any(), eq(phoneId), any(), any());
+        mPhoneSwitcherUT.mImsRegisterCallback = mMockImsRegisterCallback;
+    }
+
     @Test
     @SmallTest
     public void testNonDefaultDataPhoneInCall_ImsCallOnLte_shouldSwitchDds() throws Exception {
@@ -1038,9 +1052,9 @@
         setDefaultDataSubId(1);
         NetworkRequest internetRequest = addInternetNetworkRequest(null, 50);
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
         clearInvocations(mMockRadioConfig);
         setAllPhonesInactive();
         // Initialization done.
@@ -1050,18 +1064,18 @@
         notifyPhoneAsInCall(mPhone2);
         verify(mMockRadioConfig, never()).setPreferredDataModem(anyInt(), any());
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
 
         // Phone2 has active call, and data is on. So data switch to it.
         doReturn(true).when(mPhone).isUserDataEnabled();
         notifyDataEnabled(true);
         verify(mMockRadioConfig).setPreferredDataModem(eq(1), any());
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         clearInvocations(mMockRadioConfig);
 
         // Phone2(nDDS) call ended. But Phone1 having cross-SIM call. Don't switch.
@@ -1070,9 +1084,9 @@
         notifyPhoneAsInactive(mPhone2);
         verify(mMockRadioConfig, never()).setPreferredDataModem(anyInt(), any());
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
 
         // Phone(DDS) call ended.
         // Honor auto data switch's suggestion: if DDS is OOS, auto switch to Phone2(nDDS).
@@ -1083,9 +1097,9 @@
         // verify immediately switch back to DDS upon call ends
         verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
 
         // verify the attempt to do auto data switch to Phone2(nDDS)
         processAllFutureMessages();
@@ -1098,9 +1112,9 @@
         notifyPhoneAsInHoldingCall(mPhone2);
         verify(mMockRadioConfig, never()).setPreferredDataModem(anyInt(), any());
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
     }
 
     @Test
@@ -1115,9 +1129,9 @@
         setDefaultDataSubId(1);
         NetworkRequest internetRequest = addInternetNetworkRequest(null, 50);
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
         clearInvocations(mMockRadioConfig);
         setAllPhonesInactive();
         // Initialization done.
@@ -1127,17 +1141,17 @@
         notifyPhoneAsInCall(mPhone2);
         verify(mMockRadioConfig).setPreferredDataModem(eq(1), any());
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
 
         // During the active call, user turns off data, should immediately switch back to DDS
         notifyDataEnabled(false);
         verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
     }
 
     @Test
@@ -1165,16 +1179,16 @@
         setDefaultDataSubId(1);
         NetworkRequest internetRequest = addInternetNetworkRequest(2, 50);
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
 
         // Restricted network request will should be applied.
         internetRequest = addInternetNetworkRequest(2, 50, true);
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
     }
 
     @Test
@@ -1518,6 +1532,53 @@
     }
 
     @Test
+    public void testSetPreferredDataCallback_voiceCall() throws Exception {
+        doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
+        initialize();
+        setAllPhonesInactive();
+
+        // Phone 0 has sub 1, phone 1 has sub 2.
+        // Sub 1 is default data sub.
+        // Both are active subscriptions are active sub, as they are in both active slots.
+        setSlotIndexToSubId(0, 1);
+        setSlotIndexToSubId(1, 2);
+        setDefaultDataSubId(1);
+        assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId());
+
+        doReturn(new SubscriptionInfoInternal.Builder(mSubscriptionManagerService
+                .getSubscriptionInfoInternal(2)).setOpportunistic(1).build())
+                .when(mSubscriptionManagerService).getSubscriptionInfoInternal(2);
+
+        // First temporarily switched to the opportunistic sub 2
+        mPhoneSwitcherUT.trySetOpportunisticDataSubscription(2, false, mSetOpptDataCallback1);
+        processAllMessages();
+        mPhoneSwitcherUT.mValidationCallback.onNetworkAvailable(null, 2);
+        processAllMessages();
+        verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_SUCCESS);
+
+        // Voice call led back to default sub 1
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        doReturn(true).when(mPhone).isUserDataEnabled();
+        doReturn(true).when(mDataSettingsManager).isDataEnabled();
+        mockImsRegTech(0, REGISTRATION_TECH_LTE);
+        notifyPhoneAsInCall(mPhone);
+
+        assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId());
+        assertEquals(2, mPhoneSwitcherUT.getAutoSelectedDataSubId());
+
+        // CBRS set preferred data back to default during the phone call
+        clearInvocations(mSetOpptDataCallback1);
+        mPhoneSwitcherUT.trySetOpportunisticDataSubscription(SubscriptionManager
+                .DEFAULT_SUBSCRIPTION_ID, false, mSetOpptDataCallback1);
+        processAllMessages();
+
+        verify(mSetOpptDataCallback1).onComplete(SET_OPPORTUNISTIC_SUB_SUCCESS);
+        assertEquals(1, mPhoneSwitcherUT.getActiveDataSubId());
+        assertEquals(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                mPhoneSwitcherUT.getAutoSelectedDataSubId());
+    }
+
+    @Test
     @SmallTest
     public void testMultiSimConfigChange() throws Exception {
         doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
@@ -1558,9 +1619,9 @@
         setDefaultDataSubId(1);
         NetworkRequest internetRequest = addInternetNetworkRequest(null, 50);
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
         clearInvocations(mMockRadioConfig);
         setAllPhonesInactive();
         // Initialization done.
@@ -1599,9 +1660,9 @@
         setDefaultDataSubId(1);
         NetworkRequest internetRequest = addInternetNetworkRequest(null, 50);
         assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 0));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 0));
         assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                new TelephonyNetworkRequest(internetRequest, mPhone), 1));
+                new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), 1));
         clearInvocations(mMockRadioConfig);
         setAllPhonesInactive();
         // Initialization done.
@@ -1709,6 +1770,101 @@
         verify(mCommandsInterface1, never()).setDataAllowed(anyBoolean(), any());
     }
 
+    @Test
+    @SmallTest
+    public void testRegisterForImsRegistrationCallback() throws Exception {
+        initialize();
+        setAllPhonesInactive();
+
+        // Phone 0 has sub 1, phone 1 has sub 2.
+        // Sub 1 is default data sub.
+        // Both are active subscriptions are active sub, as they are in both active slots.
+        setSlotIndexToSubId(0, 1);
+        setSlotIndexToSubId(1, 2);
+        setDefaultDataSubId(1);
+        processAllMessages();
+
+        // Phone 0 should be the default data phoneId.
+        assertEquals(0, mPhoneSwitcherUT.getPreferredDataPhoneId());
+
+        doReturn(true).when(mPhone).isUserDataEnabled();
+        mockImsRegTech(0, REGISTRATION_TECH_LTE);
+        mockImsRegisterCallback(0);
+        mockImsRegisterCallback(1);
+
+        notifyImsRegistrationTechChange(mPhone);
+
+        // Verify that the callback is re-registered when the IMS registration callback is called.
+        verify(mMockImsRegisterCallback, times(2)).setCallback(any(), anyInt(), any(), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testReceivingImsRegistrationTech() throws Exception {
+        doReturn(true).when(mFeatureFlags).changeMethodOfObtainingImsRegistrationRadioTech();
+
+        // Set up input and output for testing
+        ImsPhone testImsPhone = mock(ImsPhone.class);
+        doReturn(testImsPhone).when(mPhone).getImsPhone();
+        doReturn(testImsPhone).when(mPhone2).getImsPhone();
+        ImsPhoneCall testImsPhoneCall = mock(ImsPhoneCall.class);
+        doReturn(Call.State.IDLE).when(testImsPhoneCall).getState();
+        doReturn(true).when(testImsPhoneCall).isIdle();
+        doReturn(testImsPhoneCall).when(testImsPhone).getForegroundCall();
+        doReturn(testImsPhoneCall).when(testImsPhone).getBackgroundCall();
+        doReturn(testImsPhoneCall).when(testImsPhone).getRingingCall();
+
+        doNothing().when(testImsPhone).registerForImsRegistrationChanges(any(), anyInt(), any());
+
+        initialize();
+        setAllPhonesInactive();
+
+        // Phone 0 has sub 1, phone 1 has sub 2.
+        // Sub 1 is default data sub.
+        // Both are active subscriptions are active sub, as they are in both active slots.
+        setSlotIndexToSubId(0, 1);
+        setSlotIndexToSubId(1, 2);
+        setDefaultDataSubId(1);
+        processAllMessages();
+
+        // Phone 0 should be the default data phoneId.
+        assertEquals(0, mPhoneSwitcherUT.getPreferredDataPhoneId());
+
+        doReturn(true).when(mPhone).isUserDataEnabled();
+        mockImsRegTech(0, REGISTRATION_TECH_NONE);
+        mockImsRegisterCallback(0);
+        mockImsRegisterCallback(1);
+
+        AsyncResult ar = new AsyncResult(null, new ImsPhone.ImsRegistrationRadioTechInfo(
+                0, REGISTRATION_TECH_LTE, REGISTRATION_STATE_REGISTERED), null);
+        notifyImsRegistrationTechChangeWithAsyncResult(ar);
+
+        // Verify cached IMS registration tech is LTE
+        assertTrue(REGISTRATION_TECH_LTE == mPhoneSwitcherUT.mImsRegistrationRadioTechMap.get(0));
+
+        ar = new AsyncResult(null, new ImsPhone.ImsRegistrationRadioTechInfo(
+                0, REGISTRATION_TECH_IWLAN, REGISTRATION_STATE_REGISTERED), null);
+        notifyImsRegistrationTechChangeWithAsyncResult(ar);
+
+        // Verify cached IMS registration tech is WiFi
+        assertTrue(REGISTRATION_TECH_IWLAN
+                == mPhoneSwitcherUT.mImsRegistrationRadioTechMap.get(0));
+
+        ar = new AsyncResult(null, new ImsPhone.ImsRegistrationRadioTechInfo(
+                0, REGISTRATION_TECH_NONE, REGISTRATION_STATE_NOT_REGISTERED), null);
+        notifyImsRegistrationTechChangeWithAsyncResult(ar);
+
+        // Verify cached IMS registration tech is NONE
+        assertTrue(REGISTRATION_TECH_NONE == mPhoneSwitcherUT.mImsRegistrationRadioTechMap.get(0));
+
+        // Verify there is no crash
+        notifyImsRegistrationTechChangeWithAsyncResult(null);
+
+        // Verify that the callback is not re-registered
+        // when the IMS registration callback is called.
+        verify(mMockImsRegisterCallback, never()).setCallback(any(), anyInt(), any(), any());
+    }
+
     /* Private utility methods start here */
 
     private void prepareIdealAutoSwitchCondition() {
@@ -1815,6 +1971,12 @@
         processAllMessages();
     }
 
+    private void notifyImsRegistrationTechChangeWithAsyncResult(AsyncResult ar) {
+        mPhoneSwitcherUT.sendMessage(
+                mPhoneSwitcherUT.obtainMessage(EVENT_IMS_RADIO_TECH_CHANGED, ar));
+        processAllMessages();
+    }
+
     private Message getEcbmRegistration(Phone phone) {
         ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
         ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);
@@ -1862,10 +2024,10 @@
             if (defaultDataSub == (i + 1)) {
                 // sub id is always phoneId+1 for testing
                 assertTrue(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                        new TelephonyNetworkRequest(internetRequest, mPhone), i));
+                        new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), i));
             } else {
                 assertFalse(mPhoneSwitcherUT.shouldApplyNetworkRequest(
-                        new TelephonyNetworkRequest(internetRequest, mPhone), i));
+                        new TelephonyNetworkRequest(internetRequest, mPhone, mFeatureFlags), i));
             }
         }
     }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkFactoryTest.java
index ad99eaf..e1a0eac 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkFactoryTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkFactoryTest.java
@@ -135,7 +135,8 @@
     }
 
     private void activatePhoneInPhoneSwitcher(int phoneId, NetworkRequest nr, boolean active) {
-        TelephonyNetworkRequest networkRequest = new TelephonyNetworkRequest(nr, mPhone);
+        TelephonyNetworkRequest networkRequest =
+                new TelephonyNetworkRequest(nr, mPhone, mFeatureFlags);
         doReturn(active).when(mPhoneSwitcher).shouldApplyNetworkRequest(
                 eq(networkRequest), eq(phoneId));
         mTelephonyNetworkFactoryUT.mInternalHandler.sendEmptyMessage(
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 26a9fde..9f11a3a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkRequestTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/TelephonyNetworkRequestTest.java
@@ -18,6 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
 import android.net.ConnectivityManager;
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
@@ -123,7 +126,7 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .build();
         TelephonyNetworkRequest internetRequest =
-                new TelephonyNetworkRequest(nativeRequest, mPhone);
+                new TelephonyNetworkRequest(nativeRequest, mPhone, mFeatureFlags);
         assertThat(internetRequest.getNativeNetworkRequest()).isEqualTo(nativeRequest);
     }
 
@@ -132,11 +135,11 @@
         TelephonyNetworkRequest internetRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         TelephonyNetworkRequest imsRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_IMS)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
 
         assertThat(internetRequest.getPriority()).isEqualTo(20);
         assertThat(imsRequest.getPriority()).isEqualTo(40);
@@ -152,7 +155,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                         .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                         .setNetworkSpecifier(telephonyNetworkSpecifier)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         assertThat(internetRequest.getNetworkSpecifier()).isEqualTo(telephonyNetworkSpecifier);
     }
 
@@ -164,19 +167,13 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
-                        .build(), mPhone);
-        assertThat(internetRequest.getCapabilities()).isEqualTo(
-                new int[]{NetworkCapabilities.NET_CAPABILITY_INTERNET,
-                        NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED,
-                        NetworkCapabilities.NET_CAPABILITY_TRUSTED,
-                        NetworkCapabilities.NET_CAPABILITY_NOT_VPN,
-                        NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED});
+                        .build(), mPhone, mFeatureFlags);
+        assertThat(internetRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED))
+                .isTrue();
         assertThat(internetRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN))
                 .isTrue();
         assertThat(internetRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET))
                 .isTrue();
-        assertThat(internetRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN))
-                .isTrue();
         assertThat(internetRequest.hasCapability(
                 NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)).isTrue();
         assertThat(internetRequest.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS))
@@ -191,7 +188,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         assertThat(request.getApnTypeNetworkCapability())
                 .isEqualTo(NetworkCapabilities.NET_CAPABILITY_SUPL);
 
@@ -200,7 +197,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         assertThat(request.getApnTypeNetworkCapability())
                 .isEqualTo(NetworkCapabilities.NET_CAPABILITY_FOTA);
 
@@ -209,7 +206,7 @@
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_EIMS)
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         assertThat(request.getApnTypeNetworkCapability())
                 .isEqualTo(NetworkCapabilities.NET_CAPABILITY_EIMS);
     }
@@ -219,7 +216,7 @@
         TelephonyNetworkRequest internetRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         NetworkCapabilities caps = new NetworkCapabilities.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
@@ -233,7 +230,7 @@
         TelephonyNetworkRequest rcsRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_RCS)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         caps = new NetworkCapabilities.Builder()
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_RCS)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
@@ -250,15 +247,15 @@
         TelephonyNetworkRequest internetRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         TelephonyNetworkRequest mmsRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         TelephonyNetworkRequest rcsRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_RCS)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         DataProfile internetDataProfile = new DataProfile.Builder()
                 .setApnSetting(INTERNET_APN_SETTING)
                 .build();
@@ -281,12 +278,12 @@
         TelephonyNetworkRequest enterpriseRequest1 = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         TelephonyNetworkRequest enterpriseRequest2 = new TelephonyNetworkRequest(
                 new NetworkRequest(new NetworkCapabilities()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
                         .addEnterpriseId(2), ConnectivityManager.TYPE_NONE,
-                        0, NetworkRequest.Type.REQUEST), mPhone);
+                        0, NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags);
 
         DataProfile enterpriseDataProfile1 = new DataProfile.Builder()
                 .setTrafficDescriptor(new TrafficDescriptor(null, new TrafficDescriptor.OsAppId(
@@ -309,18 +306,18 @@
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
         TelephonyNetworkRequest enterpriseRequest2 = new TelephonyNetworkRequest(
                 new NetworkRequest(new NetworkCapabilities()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_ENTERPRISE)
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                         .addEnterpriseId(2), ConnectivityManager.TYPE_NONE,
-                        0, NetworkRequest.Type.REQUEST), mPhone);
+                        0, NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags);
         TelephonyNetworkRequest internetRequest = new TelephonyNetworkRequest(
                 new NetworkRequest(new NetworkCapabilities()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET),
                         ConnectivityManager.TYPE_NONE,
-                        0, NetworkRequest.Type.REQUEST), mPhone);
+                        0, NetworkRequest.Type.REQUEST), mPhone, mFeatureFlags);
 
         DataProfile enterpriseDataProfile = new DataProfile.Builder()
                 .setApnSetting(ENTERPRISE_APN_SETTING)
@@ -342,12 +339,12 @@
         TelephonyNetworkRequest urllcRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
 
         TelephonyNetworkRequest embbRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
 
         DataProfile urllcDataProfile = new DataProfile.Builder()
                 .setTrafficDescriptor(new TrafficDescriptor(null, new TrafficDescriptor.OsAppId(
@@ -364,12 +361,12 @@
         TelephonyNetworkRequest urllcRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
 
         TelephonyNetworkRequest embbRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
 
         DataProfile embbDataProfile = new DataProfile.Builder()
                 .setTrafficDescriptor(new TrafficDescriptor(null, new TrafficDescriptor.OsAppId(
@@ -386,12 +383,12 @@
         TelephonyNetworkRequest cbsRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
 
         TelephonyNetworkRequest embbRequest = new TelephonyNetworkRequest(
                 new NetworkRequest.Builder()
                         .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
-                        .build(), mPhone);
+                        .build(), mPhone, mFeatureFlags);
 
         DataProfile cbsDataProfile = new DataProfile.Builder()
                 .setTrafficDescriptor(new TrafficDescriptor(null, new TrafficDescriptor.OsAppId(
@@ -402,4 +399,92 @@
         assertThat(cbsRequest.canBeSatisfiedBy(cbsDataProfile)).isTrue();
         assertThat(embbRequest.canBeSatisfiedBy(cbsDataProfile)).isFalse();
     }
+
+    @Test
+    public void testSatelliteNetworkRequest() {
+        when(mFeatureFlags.satelliteInternet()).thenReturn(true);
+        TelephonyNetworkRequest satelliteRequest = new TelephonyNetworkRequest(
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        .addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE)
+                        .build(), mPhone, mFeatureFlags);
+
+        TelephonyNetworkRequest generalRequest = new TelephonyNetworkRequest(
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        .build(), mPhone, mFeatureFlags);
+
+        ApnSetting satelliteInternetApn = new ApnSetting.Builder()
+                .setEntryName("apn")
+                .setApnName("apn")
+                .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT)
+                .setCarrierEnabled(true)
+                .setInfrastructureBitmask(ApnSetting.INFRASTRUCTURE_SATELLITE)
+                .build();
+
+        ApnSetting cellularInternetApn = new ApnSetting.Builder()
+                .setEntryName("apn")
+                .setApnName("apn")
+                .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT)
+                .setCarrierEnabled(true)
+                .setInfrastructureBitmask(ApnSetting.INFRASTRUCTURE_CELLULAR)
+                .build();
+
+        DataProfile satelliteInternetDataProfile = new DataProfile.Builder()
+                .setApnSetting(satelliteInternetApn)
+                .build();
+
+        DataProfile cellularInternetDataProfile = new DataProfile.Builder()
+                .setApnSetting(cellularInternetApn)
+                .build();
+
+        assertThat(satelliteRequest.canBeSatisfiedBy(satelliteInternetDataProfile)).isTrue();
+        assertThat(generalRequest.canBeSatisfiedBy(satelliteInternetDataProfile)).isTrue();
+        assertThat(satelliteRequest.canBeSatisfiedBy(cellularInternetDataProfile)).isFalse();
+        assertThat(generalRequest.canBeSatisfiedBy(cellularInternetDataProfile)).isTrue();
+    }
+
+    @Test
+    public void testCellularNetworkRequest() {
+        doReturn(true).when(mFeatureFlags).satelliteInternet();
+        TelephonyNetworkRequest cellularRequest = new TelephonyNetworkRequest(
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+                        .build(), mPhone, mFeatureFlags);
+
+        TelephonyNetworkRequest generalRequest = new TelephonyNetworkRequest(
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        .build(), mPhone, mFeatureFlags);
+
+        ApnSetting satelliteInternetApn = new ApnSetting.Builder()
+                .setEntryName("apn")
+                .setApnName("apn")
+                .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT)
+                .setCarrierEnabled(true)
+                .setInfrastructureBitmask(ApnSetting.INFRASTRUCTURE_SATELLITE)
+                .build();
+
+        ApnSetting cellularInternetApn = new ApnSetting.Builder()
+                .setEntryName("apn")
+                .setApnName("apn")
+                .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT)
+                .setCarrierEnabled(true)
+                .setInfrastructureBitmask(ApnSetting.INFRASTRUCTURE_CELLULAR)
+                .build();
+
+        DataProfile satelliteInternetDataProfile = new DataProfile.Builder()
+                .setApnSetting(satelliteInternetApn)
+                .build();
+
+        DataProfile cellularInternetDataProfile = new DataProfile.Builder()
+                .setApnSetting(cellularInternetApn)
+                .build();
+
+        assertThat(cellularRequest.canBeSatisfiedBy(satelliteInternetDataProfile)).isFalse();
+        assertThat(generalRequest.canBeSatisfiedBy(satelliteInternetDataProfile)).isTrue();
+        assertThat(cellularRequest.canBeSatisfiedBy(cellularInternetDataProfile)).isTrue();
+        assertThat(generalRequest.canBeSatisfiedBy(cellularInternetDataProfile)).isTrue();
+    }
 }
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 47f8ce2..5fd7a77 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionConnectionTest.java
@@ -22,6 +22,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -42,8 +43,10 @@
 import android.telecom.PhoneAccount;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.AccessNetworkConstants.AccessNetworkType;
+import android.telephony.DisconnectCause;
 import android.telephony.DomainSelectionService;
 import android.telephony.EmergencyRegistrationResult;
+import android.telephony.PreciseDisconnectCause;
 import android.telephony.data.ApnSetting;
 import android.telephony.ims.ImsReasonInfo;
 import android.testing.AndroidTestingRunner;
@@ -773,6 +776,26 @@
         verify(mDomainSelectionController, times(1)).selectDomain(any(), eq(transportCallback));
     }
 
+    @Test
+    @SmallTest
+    public void testSetDisconnectCause() throws Exception {
+        mDsc = createConnection(mPhone, SELECTOR_TYPE_CALLING, true,
+                mDomainSelectionController);
+
+        assertEquals(DisconnectCause.NOT_VALID, mDsc.getDisconnectCause());
+        assertEquals(PreciseDisconnectCause.NOT_VALID, mDsc.getPreciseDisconnectCause());
+        assertEquals(mPhone.getPhoneId(), mDsc.getPhoneId());
+
+        String reason = "No SIM or SIM error";
+        mDsc.setDisconnectCause(DisconnectCause.ICC_ERROR,
+                PreciseDisconnectCause.NO_VALID_SIM, reason);
+
+        assertEquals(DisconnectCause.ICC_ERROR, mDsc.getDisconnectCause());
+        assertEquals(PreciseDisconnectCause.NO_VALID_SIM, mDsc.getPreciseDisconnectCause());
+        assertEquals(reason, mDsc.getReasonMessage());
+        assertEquals(mPhone.getPhoneId(), mDsc.getPhoneId());
+    }
+
     private DomainSelectionConnection createConnection(Phone phone, int selectorType,
             boolean isEmergency, DomainSelectionController controller) throws Exception {
         DomainSelectionConnection dsc = new DomainSelectionConnection(phone,
diff --git a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionControllerTest.java
index f5ccdd6..a55ad69 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/domainselection/DomainSelectionControllerTest.java
@@ -16,8 +16,11 @@
 
 package com.android.internal.telephony.domainselection;
 
+import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -36,6 +39,8 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.telephony.DomainSelectionService;
+import android.telephony.TelephonyManager;
+import android.test.mock.MockContext;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -71,6 +76,8 @@
                 }
             };
 
+    Context mTestContext;
+
     // Mocked classes
     IDomainSelectionServiceController mMockServiceControllerBinder;
     Context mMockContext;
@@ -84,9 +91,40 @@
     public void setUp() throws Exception {
         super.setUp(this.getClass().getSimpleName());
 
+        when(mTelephonyManager.getSupportedModemCount()).thenReturn(2);
         mMockContext = mock(Context.class);
+        mTestContext = new MockContext() {
+            @Override
+            public String getSystemServiceName(Class<?> serviceClass) {
+                if (serviceClass == TelephonyManager.class) {
+                    return Context.TELEPHONY_SERVICE;
+                }
+                return super.getSystemServiceName(serviceClass);
+            }
+
+            @Override
+            public Object getSystemService(String name) {
+                switch (name) {
+                    case (Context.TELEPHONY_SERVICE) : {
+                        return mTelephonyManager;
+                    }
+                }
+                return super.getSystemService(name);
+            }
+
+            @Override
+            public boolean bindService(Intent service, ServiceConnection conn, int flags) {
+                return mMockContext.bindService(service, conn, flags);
+            }
+
+            @Override
+            public void unbindService(ServiceConnection conn) {
+                mMockContext.unbindService(conn);
+            }
+        };
+
         mMockServiceControllerBinder = mock(IDomainSelectionServiceController.class);
-        mTestController = new DomainSelectionController(mMockContext,
+        mTestController = new DomainSelectionController(mTestContext,
                 Looper.getMainLooper(), BIND_RETRY);
         mHandler = mTestController.getHandlerForTest();
 
@@ -264,6 +302,17 @@
         verify(mMockContext, times(1)).bindService(any(), any(), anyInt());
     }
 
+    @SmallTest
+    @Test
+    public void testGetDomainSelectionConnection() throws Exception {
+        when(mPhone.getPhoneId()).thenReturn(1);
+        DomainSelectionConnection dsc = mTestController.getDomainSelectionConnection(
+                mPhone, SELECTOR_TYPE_CALLING, false);
+
+        assertNotNull(dsc);
+        assertTrue(dsc instanceof NormalCallDomainSelectionConnection);
+    }
+
     private void bindAndNullServiceError() {
         ServiceConnection connection = bindService(mTestComponentName);
         connection.onNullBinding(mTestComponentName);
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 3eb7659..f05099d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/EmergencyStateTrackerTest.java
@@ -36,6 +36,7 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyVararg;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
@@ -49,10 +50,12 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.AccessNetworkConstants;
@@ -69,9 +72,12 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.GsmCdmaPhone;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.data.PhoneSwitcher;
 
@@ -112,6 +118,9 @@
     public void setUp() throws Exception {
         super.setUp(getClass().getSimpleName());
         MockitoAnnotations.initMocks(this);
+
+        doReturn(TelephonyManager.SIM_STATE_READY)
+                .when(mTelephonyManagerProxy).getSimState(anyInt());
     }
 
     @After
@@ -254,6 +263,46 @@
         verify(testPhone).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any());
     }
 
+    @Test
+    @SmallTest
+    public void startEmergencyCall_radioOff_turnOnRadioHangupCallTurnOffRadio() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                true /* isSuplDdsSwitchRequiredForEmergencyCall */);
+        // Create test Phones and set radio off
+        Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                false /* isRadioOn */);
+        setConfigForDdsSwitch(testPhone, null,
+                CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY, "150");
+        ServiceState ss = mock(ServiceState.class);
+        doReturn(ss).when(mSST).getServiceState();
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .build();
+        doReturn(nri).when(ss).getNetworkRegistrationInfo(anyInt(), anyInt());
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+
+        // startEmergencyCall should trigger radio on
+        ArgumentCaptor<RadioOnStateListener.Callback> callback = ArgumentCaptor
+                .forClass(RadioOnStateListener.Callback.class);
+        verify(mRadioOnHelper).triggerRadioOnAndListen(callback.capture(), eq(true), eq(testPhone),
+                eq(false), eq(DEFAULT_WAIT_FOR_IN_SERVICE_TIMEOUT_MS));
+
+        // Hangup the call
+        emergencyStateTracker.endCall(mTestConnection1);
+
+        // onTimeout and isOkToCall should return true even in case radion is off
+        assertTrue(callback.getValue()
+                .isOkToCall(testPhone, ServiceState.STATE_POWER_OFF, false));
+        assertTrue(callback.getValue()
+                .onTimeout(testPhone, ServiceState.STATE_POWER_OFF, false));
+
+        callback.getValue().onComplete(null, true);
+
+        assertFalse(future.isDone());
+    }
+
     /**
      * Test that if startEmergencyCall fails to turn on radio, then it's future completes with
      * DisconnectCause.POWER_OFF.
@@ -571,6 +620,9 @@
         assertTrue(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInCdmaEcm());
         assertTrue(emergencyStateTracker.isInImsEcm());
+
+        assertTrue(emergencyStateTracker.isInEcm(testPhone));
+        assertFalse(emergencyStateTracker.isInEcm(getPhone(1)));
     }
 
     /**
@@ -1106,7 +1158,7 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_CS);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_CS, true);
 
         verify(phone0).exitEmergencyMode(any(Message.class));
         assertFalse(emergencyStateTracker.isInEmergencyMode());
@@ -1116,6 +1168,39 @@
 
     @Test
     @SmallTest
+    public void testEndSmsForMultipartMessage() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        assertTrue(emergencyStateTracker.getEmergencyRegistrationResult().equals(E_REG_RESULT));
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        // First SMS part of multipart message.
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_CS, false);
+
+        // exitEmergencyMode is not called and it's still in emergency mode.
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+
+        // Last SMS part is sent successfully.
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_CS, true);
+
+        verify(phone0).exitEmergencyMode(any(Message.class));
+        assertFalse(emergencyStateTracker.isInEmergencyMode());
+    }
+
+    @Test
+    @SmallTest
     public void testEndSmsAndEnterEmergencySmsCallbackMode() {
         EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
                 /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
@@ -1134,7 +1219,7 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS, true);
 
         verify(mCarrierConfigManager).getConfigForSubId(anyInt(),
                 eq(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT));
@@ -1166,7 +1251,7 @@
                 EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN);
 
         // When MO SMS fails while in SCBM.
-        emergencyStateTracker.endSms(TEST_SMS_ID_2, false, DOMAIN_PS);
+        emergencyStateTracker.endSms(TEST_SMS_ID_2, false, DOMAIN_PS, true);
 
         verify(mCarrierConfigManager).getConfigForSubId(anyInt(),
                 eq(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT));
@@ -1198,7 +1283,7 @@
                 EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN);
 
         // When MO SMS is successfully sent while in SCBM.
-        emergencyStateTracker.endSms(TEST_SMS_ID_2, true, DOMAIN_PS);
+        emergencyStateTracker.endSms(TEST_SMS_ID_2, true, DOMAIN_PS, true);
 
         verify(mCarrierConfigManager, times(2)).getConfigForSubId(anyInt(),
                 eq(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT));
@@ -1627,7 +1712,7 @@
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
         assertFalse(emergencyStateTracker.isInScbm());
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS, true);
 
         assertTrue(emergencyStateTracker.isInScbm());
         assertTrue(emergencyStateTracker.isInEmergencyMode());
@@ -1749,7 +1834,7 @@
         assertFalse(smsFuture.isDone());
         assertFalse(callFuture.isDone());
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, false, DOMAIN_PS);
+        emergencyStateTracker.endSms(TEST_SMS_ID, false, DOMAIN_PS, true);
 
         // Response message for setEmergencyMode by SMS.
         Message msg = smsCaptor.getValue();
@@ -1983,7 +2068,7 @@
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS, true);
         processAllMessages();
 
         assertFalse(emergencyStateTracker.isInEmergencyMode());
@@ -2020,7 +2105,7 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS, true);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
 
@@ -2067,7 +2152,7 @@
         assertTrue(emergencyStateTracker.isInEcm());
         assertFalse(emergencyStateTracker.isInEmergencyCall());
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS, true);
         processAllMessages();
 
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
@@ -2122,7 +2207,7 @@
 
         emergencyStateTracker.onEmergencyTransportChanged(
                 EmergencyStateTracker.EMERGENCY_TYPE_SMS, MODE_EMERGENCY_WWAN);
-        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS, true);
         processAllMessages();
 
         // Enter emergency callback mode and emergency mode changed by SMS end.
@@ -2171,7 +2256,7 @@
         assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
-        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS, true);
 
         assertTrue(emergencyStateTracker.isInEmergencyMode());
 
@@ -2227,7 +2312,7 @@
         assertFalse(emergencyStateTracker.isInEmergencyCall());
 
         setScbmTimerValue(phone0, TEST_ECM_EXIT_TIMEOUT_MS + 100);
-        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS, true);
         processAllMessages();
 
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
@@ -2289,7 +2374,7 @@
         assertFalse(emergencyStateTracker.isInEmergencyCall());
 
         setScbmTimerValue(phone0, TEST_ECM_EXIT_TIMEOUT_MS - 100);
-        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS, true);
         processAllMessages();
 
         verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_CALLBACK), any(Message.class));
@@ -2324,6 +2409,8 @@
                 false /* isRadioOn */);
         when(phone.getSubId()).thenReturn(1);
         setEcmSupportedConfig(phone, true);
+        PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(phone.getSubId());
+        doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyVararg());
 
         EmergencyStateTracker testEst = setupEmergencyStateTracker(
                 false /* isSuplDdsSwitchRequiredForEmergencyCall */);
@@ -2344,6 +2431,7 @@
         // Verify carrier config for valid subscription
         assertTrue(testEst.isEmergencyCallbackModeSupported(phone));
 
+        // onCarrierConfigurationChanged is not called yet.
         // SIM removed
         when(phone.getSubId()).thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         setEcmSupportedConfig(phone, false);
@@ -2372,7 +2460,35 @@
         // Verify saved config for valid subscription
         assertTrue(testEst.isEmergencyCallbackModeSupported(phone));
 
-        // Insert SIM again, but emergency callback mode not supported
+        // Insert SIM in PIN locked again, but emergency callback mode not supported
+        doReturn(TelephonyManager.SIM_STATE_PIN_REQUIRED)
+                .when(mTelephonyManagerProxy).getSimState(anyInt());
+        when(phone.getSubId()).thenReturn(1);
+        setEcmSupportedConfig(phone, false);
+
+        // onCarrierConfigChanged with valid subscription
+        carrierConfigChangeListener.onCarrierConfigChanged(
+                phone.getPhoneId(), phone.getSubId(),
+                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+
+        // Verify carrier config for valid subscription in PIN locked state, saved configuration
+        assertTrue(testEst.isEmergencyCallbackModeSupported(phone));
+
+        // SIM removed again
+        when(phone.getSubId()).thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        setEcmSupportedConfig(phone, false);
+
+        // onCarrierConfigChanged with invalid subscription
+        carrierConfigChangeListener.onCarrierConfigChanged(
+                phone.getPhoneId(), phone.getSubId(),
+                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+
+        // Verify saved config for valid subscription
+        assertTrue(testEst.isEmergencyCallbackModeSupported(phone));
+
+        // Insert SIM, PIN verified, again, but emergency callback mode not supported
+        doReturn(TelephonyManager.SIM_STATE_READY)
+                .when(mTelephonyManagerProxy).getSimState(anyInt());
         when(phone.getSubId()).thenReturn(1);
         setEcmSupportedConfig(phone, false);
 
@@ -2559,6 +2675,502 @@
         verify(testPhone).exitEmergencyMode(any(Message.class));
     }
 
+    /**
+     * Test that the EmergencyStateTracker rejects incoming call when starting an emergency call.
+     */
+    @Test
+    @SmallTest
+    public void testRejectRingingCall() {
+        Phone phone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                false /* isRadioOn */);
+        when(phone.getSubId()).thenReturn(1);
+        Connection c = mock(Connection.class);
+        Call call = mock(Call.class);
+        doReturn(c).when(call).getLatestConnection();
+        doReturn(Call.State.INCOMING).when(call).getState();
+        doReturn(call).when(phone).getRingingCall();
+        setEcmSupportedConfig(phone, true);
+
+        EmergencyStateTracker testEst = setupEmergencyStateTracker(
+                false /* isSuplDdsSwitchRequiredForEmergencyCall */);
+
+        // There is an ongoing emergency call.
+        CompletableFuture<Integer> future = testEst.startEmergencyCall(phone,
+                mTestConnection1, false);
+
+        assertNotNull(future);
+
+        // Verify rejecting ringing call.
+        try {
+            verify(call).hangup();
+        } catch (CallStateException e) {
+        }
+    }
+
+    /**
+     * Test that the EmergencyStateTracker rejects incoming call if there is an emergency call
+     * in dialing state.
+     */
+    @Test
+    @SmallTest
+    public void testRejectNewIncomingCall() {
+        Phone phone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                false /* isRadioOn */);
+        when(phone.getSubId()).thenReturn(1);
+        setEcmSupportedConfig(phone, true);
+
+        EmergencyStateTracker testEst = setupEmergencyStateTracker(
+                false /* isSuplDdsSwitchRequiredForEmergencyCall */);
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        verify(phone).registerForNewRingingConnection(handlerCaptor.capture(),
+                intCaptor.capture(), any());
+        assertNotNull(handlerCaptor.getValue());
+        assertNotNull(intCaptor.getValue());
+
+        // There is an ongoing emergency call.
+        CompletableFuture<Integer> future = testEst.startEmergencyCall(phone,
+                mTestConnection1, false);
+
+        assertNotNull(future);
+
+        Connection c = mock(Connection.class);
+        Call call = mock(Call.class);
+        doReturn(call).when(c).getCall();
+
+        Message msg = Message.obtain(handlerCaptor.getValue(), intCaptor.getValue());
+        AsyncResult.forMessage(msg, c, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Verify rejecting incoming call.
+        try {
+            verify(call).hangup();
+        } catch (CallStateException e) {
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testNotRejectNewIncomingCall() {
+        Phone phone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                false /* isRadioOn */);
+        when(phone.getSubId()).thenReturn(1);
+        setEcmSupportedConfig(phone, true);
+
+        EmergencyStateTracker unused = setupEmergencyStateTracker(
+                false /* isSuplDdsSwitchRequiredForEmergencyCall */);
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        verify(phone).registerForNewRingingConnection(handlerCaptor.capture(),
+                intCaptor.capture(), any());
+        assertNotNull(handlerCaptor.getValue());
+        assertNotNull(intCaptor.getValue());
+
+        // There is no ongoing emergency call.
+
+        Connection c = mock(Connection.class);
+        Call call = mock(Call.class);
+        doReturn(call).when(c).getCall();
+
+        Message msg = Message.obtain(handlerCaptor.getValue(), intCaptor.getValue());
+        AsyncResult.forMessage(msg, c, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Verify not rejecting incoming call.
+        try {
+            verify(call, never()).hangup();
+        } catch (CallStateException e) {
+        }
+    }
+
+    /**
+     * Test that the EmergencyStateTracker rejects incoming call when starting
+     * a normal routing emergency call.
+     */
+    @Test
+    @SmallTest
+    public void testNormalRoutingEmergencyCallRejectRingingCall() {
+        Phone phone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                false /* isRadioOn */);
+        when(phone.getSubId()).thenReturn(1);
+        Connection c = mock(Connection.class);
+        Call call = mock(Call.class);
+        doReturn(c).when(call).getLatestConnection();
+        doReturn(Call.State.DISCONNECTING).when(call).getState();
+        doReturn(call).when(phone).getRingingCall();
+        setEcmSupportedConfig(phone, true);
+
+        EmergencyStateTracker testEst = setupEmergencyStateTracker(
+                false /* isSuplDdsSwitchRequiredForEmergencyCall */);
+
+        // There is an ongoing normal routing emergency call.
+        testEst.startNormalRoutingEmergencyCall(phone, mTestConnection1, result -> {});
+
+        // Verify rejecting ringing call.
+        try {
+            verify(call).hangup();
+        } catch (CallStateException e) {
+        }
+    }
+
+
+    /**
+     * Test that the EmergencyStateTracker rejects incoming call if there is
+     * a normal routing emergency call in dialing state.
+     */
+    @Test
+    @SmallTest
+    public void testNormalRoutingRejectNewIncomingCall() {
+        Phone phone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                false /* isRadioOn */);
+        when(phone.getSubId()).thenReturn(1);
+        setEcmSupportedConfig(phone, true);
+
+        EmergencyStateTracker testEst = setupEmergencyStateTracker(
+                false /* isSuplDdsSwitchRequiredForEmergencyCall */);
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        verify(phone).registerForNewRingingConnection(handlerCaptor.capture(),
+                intCaptor.capture(), any());
+        assertNotNull(handlerCaptor.getValue());
+        assertNotNull(intCaptor.getValue());
+
+        // There is an ongoing normal routing emergency call.
+        testEst.startNormalRoutingEmergencyCall(phone, mTestConnection1, result -> {});
+
+        Connection c = mock(Connection.class);
+        Call call = mock(Call.class);
+        doReturn(call).when(c).getCall();
+
+        Message msg = Message.obtain(handlerCaptor.getValue(), intCaptor.getValue());
+        AsyncResult.forMessage(msg, c, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Verify rejecting incoming call.
+        try {
+            verify(call).hangup();
+        } catch (CallStateException e) {
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testNormalRoutingDiscardedNotRejectNewIncomingCall() {
+        Phone phone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                false /* isRadioOn */);
+        when(phone.getSubId()).thenReturn(1);
+        setEcmSupportedConfig(phone, true);
+
+        EmergencyStateTracker testEst = setupEmergencyStateTracker(
+                false /* isSuplDdsSwitchRequiredForEmergencyCall */);
+
+        ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+        ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);
+
+        verify(phone).registerForNewRingingConnection(handlerCaptor.capture(),
+                intCaptor.capture(), any());
+        assertNotNull(handlerCaptor.getValue());
+        assertNotNull(intCaptor.getValue());
+
+        // Start normal routing emergency call.
+        testEst.startNormalRoutingEmergencyCall(phone, mTestConnection1, result -> {});
+
+        // Discard normal routing emergency call.
+        testEst.endNormalRoutingEmergencyCall(mTestConnection1);
+
+        Connection c = mock(Connection.class);
+        Call call = mock(Call.class);
+        doReturn(call).when(c).getCall();
+
+        Message msg = Message.obtain(handlerCaptor.getValue(), intCaptor.getValue());
+        AsyncResult.forMessage(msg, c, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        // Verify not rejecting incoming call.
+        try {
+            verify(call, never()).hangup();
+        } catch (CallStateException e) {
+        }
+    }
+
+    /**
+     * Test that emergency call state changes are sent.
+     */
+    @Test
+    @SmallTest
+    public void testSendEmergencyCallStateChanges() {
+        mContextFixture.getCarrierConfigBundle().putBoolean(
+                CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+        // Setup EmergencyStateTracker
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        // Create test Phone
+        Phone testPhone = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        when(testPhone.getSubId()).thenReturn(1);
+        ArgumentCaptor<CarrierConfigManager.CarrierConfigChangeListener> listenerArgumentCaptor =
+                ArgumentCaptor.forClass(CarrierConfigManager.CarrierConfigChangeListener.class);
+        CarrierConfigManager cfgManager = (CarrierConfigManager) mContext
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+
+        verify(cfgManager).registerCarrierConfigChangeListener(any(),
+                listenerArgumentCaptor.capture());
+
+        CarrierConfigManager.CarrierConfigChangeListener carrierConfigChangeListener =
+                listenerArgumentCaptor.getAllValues().get(0);
+
+        assertNotNull(carrierConfigChangeListener);
+
+        PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(testPhone.getSubId());
+        bundle.putBoolean(CarrierConfigManager.KEY_BROADCAST_EMERGENCY_CALL_STATE_CHANGES_BOOL,
+                true);
+        doReturn(bundle).when(mCarrierConfigManager).getConfigForSubId(anyInt(), anyVararg());
+        // onCarrierConfigChanged with valid subscription
+        carrierConfigChangeListener.onCarrierConfigChanged(
+                testPhone.getPhoneId(), testPhone.getSubId(),
+                TelephonyManager.UNKNOWN_CARRIER_ID, TelephonyManager.UNKNOWN_CARRIER_ID);
+
+        // Start emergency call
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+
+        // Verify intent is sent that emergency call state is changed
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(1)).sendStickyBroadcastAsUser(
+                intentCaptor.capture(), eq(UserHandle.ALL));
+        Intent intent = intentCaptor.getValue();
+        assertNotNull(intent);
+        assertEquals(TelephonyIntents.ACTION_EMERGENCY_CALL_STATE_CHANGED, intent.getAction());
+        assertTrue(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_EMERGENCY_CALL, true));
+
+        // End emergency call
+        emergencyStateTracker.endCall(mTestConnection1);
+
+        // Verify intent is sent that emergency call state is changed
+        verify(mContext, times(2)).sendStickyBroadcastAsUser(
+                intentCaptor.capture(), eq(UserHandle.ALL));
+        intent = intentCaptor.getValue();
+        assertNotNull(intent);
+        assertEquals(TelephonyIntents.ACTION_EMERGENCY_CALL_STATE_CHANGED, intent.getAction());
+        assertFalse(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_EMERGENCY_CALL, false));
+    }
+
+    /**
+     * Test that emergency call state change is reset after crash.
+     */
+    @Test
+    @SmallTest
+    public void testResetEmergencyCallStateChanges() {
+        Intent intent = new Intent(TelephonyIntents.ACTION_EMERGENCY_CALL_STATE_CHANGED);
+        intent.putExtra(TelephonyManager.EXTRA_PHONE_IN_EMERGENCY_CALL, true);
+        doReturn(intent).when(mContext).registerReceiver(eq(null), any(),
+                eq(Context.RECEIVER_NOT_EXPORTED));
+        // Setup EmergencyStateTracker
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        emergencyStateTracker.maybeResetEmergencyCallStateChangedIntent();
+
+        ArgumentCaptor<IntentFilter> filterCaptor = ArgumentCaptor.forClass(IntentFilter.class);
+
+        verify(mContext).registerReceiver(eq(null), filterCaptor.capture(),
+                eq(Context.RECEIVER_NOT_EXPORTED));
+
+        IntentFilter filter = filterCaptor.getValue();
+
+        assertNotNull(filter);
+        assertTrue(filter.hasAction(TelephonyIntents.ACTION_EMERGENCY_CALL_STATE_CHANGED));
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+        // Verify intent is sent that emergency call state is changed
+        verify(mContext).sendStickyBroadcastAsUser(
+                intentCaptor.capture(), eq(UserHandle.ALL));
+        intent = intentCaptor.getValue();
+        assertNotNull(intent);
+        assertEquals(TelephonyIntents.ACTION_EMERGENCY_CALL_STATE_CHANGED, intent.getAction());
+        assertFalse(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_EMERGENCY_CALL, false));
+    }
+
+    /**
+     * Test that emergency call state is not reset after crash
+     * if it's already reset.
+     */
+    @Test
+    @SmallTest
+    public void testResetEmergencyCallStateChangesAlreadyReset() {
+        Intent intent = new Intent(TelephonyIntents.ACTION_EMERGENCY_CALL_STATE_CHANGED);
+        intent.putExtra(TelephonyManager.EXTRA_PHONE_IN_EMERGENCY_CALL, false);
+        doReturn(intent).when(mContext).registerReceiver(eq(null), any(),
+                eq(Context.RECEIVER_NOT_EXPORTED));
+        // Setup EmergencyStateTracker
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        emergencyStateTracker.maybeResetEmergencyCallStateChangedIntent();
+
+        ArgumentCaptor<IntentFilter> filterCaptor = ArgumentCaptor.forClass(IntentFilter.class);
+
+        verify(mContext).registerReceiver(eq(null), filterCaptor.capture(),
+                eq(Context.RECEIVER_NOT_EXPORTED));
+
+        IntentFilter filter = filterCaptor.getValue();
+
+        assertNotNull(filter);
+        assertTrue(filter.hasAction(TelephonyIntents.ACTION_EMERGENCY_CALL_STATE_CHANGED));
+
+        // Verify intent is not sent.
+        verify(mContext, never()).sendStickyBroadcastAsUser(any(), any());
+    }
+
+    /**
+     * Test that emergency call state is not reset after crash
+     * if it has never been sent.
+     */
+    @Test
+    @SmallTest
+    public void testResetEmergencyCallStateChangesNotSent() {
+        doReturn(null).when(mContext).registerReceiver(eq(null), any(),
+                eq(Context.RECEIVER_NOT_EXPORTED));
+        // Setup EmergencyStateTracker
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        emergencyStateTracker.maybeResetEmergencyCallStateChangedIntent();
+
+        ArgumentCaptor<IntentFilter> filterCaptor = ArgumentCaptor.forClass(IntentFilter.class);
+
+        verify(mContext).registerReceiver(eq(null), filterCaptor.capture(),
+                eq(Context.RECEIVER_NOT_EXPORTED));
+
+        IntentFilter filter = filterCaptor.getValue();
+
+        assertNotNull(filter);
+        assertTrue(filter.hasAction(TelephonyIntents.ACTION_EMERGENCY_CALL_STATE_CHANGED));
+
+        // Verify intent is not sent.
+        verify(mContext, never()).sendStickyBroadcastAsUser(any(), any());
+    }
+
+    @Test
+    @SmallTest
+    public void testEnsureExecutionOrderOfExitEmergencyModeThenSetEmergencyMode() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ true,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+
+        // First trial
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection1, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        assertTrue(emergencyStateTracker.isInEmergencyCall());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
+        emergencyStateTracker.endCall(mTestConnection1);
+        processAllMessages();
+
+        verify(phone0, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+        verify(phone0).exitEmergencyMode(msgCaptor.capture());
+
+        Message msg = msgCaptor.getValue();
+
+        assertNotNull(msg);
+
+        // Second trial
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection2, false);
+        processAllMessages();
+
+        assertFalse(future.isDone());
+        verify(phone0, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        AsyncResult.forMessage(msg, null, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testEnsureExecutionOrderOfExitEmergencyModeThenSetEmergencyModeWithSms() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                /* isSuplDdsSwitchRequiredForEmergencyCall= */ true);
+        Phone phone0 = setupTestPhoneForEmergencyCall(/* isRoaming= */ false,
+                /* isRadioOn= */ true);
+        setUpAsyncResultForSetEmergencyMode(phone0, E_REG_RESULT);
+        CompletableFuture<Integer> future = emergencyStateTracker.startEmergencySms(phone0,
+                TEST_SMS_ID, false);
+        processAllMessages();
+
+        assertTrue(emergencyStateTracker.isInEmergencyMode());
+        verify(phone0).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        assertTrue(emergencyStateTracker.getEmergencyRegistrationResult().equals(E_REG_RESULT));
+        // Expect: DisconnectCause#NOT_DISCONNECTED.
+        assertEquals(future.getNow(DisconnectCause.ERROR_UNSPECIFIED),
+                Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
+
+        ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_CS, true);
+
+        verify(phone0).exitEmergencyMode(msgCaptor.capture());
+
+        Message msg = msgCaptor.getValue();
+
+        assertNotNull(msg);
+
+        // Dial emergency call
+        future = emergencyStateTracker.startEmergencyCall(phone0,
+                mTestConnection2, false);
+        processAllMessages();
+
+        assertFalse(future.isDone());
+        verify(phone0, times(1)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+
+        AsyncResult.forMessage(msg, null, null);
+        msg.sendToTarget();
+        processAllMessages();
+
+        verify(phone0, times(2)).setEmergencyMode(eq(MODE_EMERGENCY_WWAN), any(Message.class));
+    }
+
+    /**
+     * Test that the EmergencyStateTracker waits for the delayed radio power off.
+     */
+    @Test
+    @SmallTest
+    public void startEmergencyCall_delayedRadioOff_waitForRadioOff() {
+        EmergencyStateTracker emergencyStateTracker = setupEmergencyStateTracker(
+                true /* isSuplDdsSwitchRequiredForEmergencyCall */);
+        // Create test Phones and set radio on
+        Phone testPhone = setupTestPhoneForEmergencyCall(false /* isRoaming */,
+                true /* isRadioOn */);
+
+        // Airplane mode is ON, but radio power state is still ON
+        Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+
+        CompletableFuture<Integer> unused = emergencyStateTracker.startEmergencyCall(testPhone,
+                mTestConnection1, false);
+
+        // Wait for the radio off for all phones
+        verify(mSST, times(2)).registerForVoiceRegStateOrRatChanged(any(), anyInt(), any());
+        verify(mRadioOnHelper, never()).triggerRadioOnAndListen(any(), anyBoolean(), any(),
+                anyBoolean(), eq(0));
+    }
+
     private EmergencyStateTracker setupEmergencyStateTracker(
             boolean isSuplDdsSwitchRequiredForEmergencyCall) {
         doReturn(mPhoneSwitcher).when(mPhoneSwitcherProxy).getPhoneSwitcher();
@@ -2664,7 +3276,7 @@
                 Integer.valueOf(DisconnectCause.NOT_DISCONNECTED));
 
         // Expect: entering SCBM.
-        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS);
+        emergencyStateTracker.endSms(TEST_SMS_ID, true, DOMAIN_PS, true);
 
         verify(mCarrierConfigManager).getConfigForSubId(anyInt(),
                 eq(CarrierConfigManager.KEY_EMERGENCY_SMS_MODE_TIMER_MS_INT));
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 5a6fdc2..037f82c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/emergency/RadioOnStateListenerTest.java
@@ -205,7 +205,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), any());
+                anyInt(), eq(false), eq(false), eq(false), any());
     }
 
     @Test
@@ -225,7 +225,7 @@
         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), any());
+                anyInt(), eq(false), eq(false), eq(false), any());
     }
 
     @Test
@@ -246,7 +246,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), any());
+                anyInt(), 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 cc4b180..e374551 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
@@ -231,6 +231,7 @@
                 Settings.Global.EUICC_PROVISIONED, 0);
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.EUICC_PROVISIONED, 0);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(false);
     }
 
     @After
@@ -895,7 +896,7 @@
 
     @Test
     @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
-    public void testDownloadSubscription_adminPermission_usingSwitchAfterDownload()
+    public void testDownloadSubscription_adminPermission_usingSwitchAfterDownload_fails()
             throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
         setHasWriteEmbeddedPermission(false);
@@ -910,18 +911,99 @@
         when(mPackageManager.getPackageInfo(eq(PACKAGE_NAME), anyInt())).thenReturn(pi);
         setCanManageSubscriptionOnTargetSim(false /* isTargetEuicc */, false /* hasPrivileges */);
 
-        callDownloadSubscription(SUBSCRIPTION, true /* switchAfterDownload */, true /* complete */,
+        callDownloadSubscription(SUBSCRIPTION, true /* switchAfterDownload */,
+                true /* complete */,
                 12345, 0 /* resolvableError */, PACKAGE_NAME /* callingPackage */);
 
-        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR,
-                0 /* detailedCode */);
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, 0 /* detailedCode */);
         verify(mMockConnector, never()).downloadSubscription(anyInt(), anyInt(),
                 any(), anyBoolean(), anyBoolean(), any(), any());
     }
 
     @Test
     @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
-    public void testDownloadSubscription_onlyAdminManagedAllowed_callerNotAdmin_throws()
+    public void testDownloadSubscription_profileOwner_usingSwitchAfterDownload_fails()
+            throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasWriteEmbeddedPermission(false);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(true);
+        setUpUiccSlotData();
+        GetDownloadableSubscriptionMetadataResult result =
+                new GetDownloadableSubscriptionMetadataResult(EuiccService.RESULT_OK,
+                        SUBSCRIPTION_WITH_METADATA);
+        doReturn(true).when(mDevicePolicyManager).isProfileOwnerApp(PACKAGE_NAME);
+        doReturn(false).when(mDevicePolicyManager).isOrganizationOwnedDeviceWithManagedProfile();
+        doReturn(false).when(mDevicePolicyManager).isDeviceOwnerApp(PACKAGE_NAME);
+        prepareGetDownloadableSubscriptionMetadataCall(true /* complete */, result);
+        PackageInfo pi = new PackageInfo();
+        pi.packageName = PACKAGE_NAME;
+        when(mPackageManager.getPackageInfo(eq(PACKAGE_NAME), anyInt())).thenReturn(pi);
+        setCanManageSubscriptionOnTargetSim(false /* isTargetEuicc */, false /* hasPrivileges */);
+
+        callDownloadSubscription(SUBSCRIPTION, true /* switchAfterDownload */,
+                true /* complete */,
+                12345, 0 /* resolvableError */, PACKAGE_NAME /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, 0 /* detailedCode */);
+        verify(mMockConnector, never()).downloadSubscription(anyInt(), anyInt(), any(),
+                anyBoolean(), anyBoolean(), any(), any());
+    }
+
+    @Test
+    @DisableCompatChanges({EuiccManager.SHOULD_RESOLVE_PORT_INDEX_FOR_APPS})
+    public void testDownloadSubscription_orgOwnedProfileOwner_usingSwitchAfterDownload_success()
+            throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ESIM_MANAGEMENT_ENABLED);
+        setHasWriteEmbeddedPermission(false);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(true);
+        setUpUiccSlotData();
+        GetDownloadableSubscriptionMetadataResult result =
+                new GetDownloadableSubscriptionMetadataResult(EuiccService.RESULT_OK,
+                        SUBSCRIPTION_WITH_METADATA);
+        doReturn(true).when(mDevicePolicyManager).isProfileOwnerApp(PACKAGE_NAME);
+        doReturn(true).when(mDevicePolicyManager).isOrganizationOwnedDeviceWithManagedProfile();
+        doReturn(false).when(mDevicePolicyManager).isDeviceOwnerApp(PACKAGE_NAME);
+        prepareGetDownloadableSubscriptionMetadataCall(true /* complete */, result);
+        PackageInfo pi = new PackageInfo();
+        pi.packageName = PACKAGE_NAME;
+        when(mPackageManager.getPackageInfo(eq(PACKAGE_NAME), anyInt())).thenReturn(pi);
+
+        callDownloadSubscription(SUBSCRIPTION, true /* switchAfterDownload */, true /* complete */,
+                EuiccService.RESULT_OK, 0 /* resolvableError */, PACKAGE_NAME /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
+        assertFalse(mController.mCalledRefreshSubscriptionsAndSendResult);
+    }
+
+    @Test
+    @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();
+        GetDownloadableSubscriptionMetadataResult result =
+                new GetDownloadableSubscriptionMetadataResult(EuiccService.RESULT_OK,
+                        SUBSCRIPTION_WITH_METADATA);
+        doReturn(false).when(mDevicePolicyManager).isProfileOwnerApp(PACKAGE_NAME);
+        doReturn(false).when(mDevicePolicyManager).isOrganizationOwnedDeviceWithManagedProfile();
+        doReturn(true).when(mDevicePolicyManager).isDeviceOwnerApp(PACKAGE_NAME);
+        prepareGetDownloadableSubscriptionMetadataCall(true /* complete */, result);
+        PackageInfo pi = new PackageInfo();
+        pi.packageName = PACKAGE_NAME;
+        when(mPackageManager.getPackageInfo(eq(PACKAGE_NAME), anyInt())).thenReturn(pi);
+
+        callDownloadSubscription(SUBSCRIPTION, true /* switchAfterDownload */, true /* complete */,
+                EuiccService.RESULT_OK, 0 /* resolvableError */, PACKAGE_NAME /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
+        assertFalse(mController.mCalledRefreshSubscriptionsAndSendResult);
+    }
+
+    @Test
+    @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);
@@ -930,15 +1012,10 @@
                 .when(mUserManager)
                 .hasUserRestriction(UserManager.DISALLOW_SIM_GLOBALLY);
 
-        assertThrows(SecurityException.class,
-                () ->
-                        callDownloadSubscription(
-                                SUBSCRIPTION,
-                                false /* switchAfterDownload */,
-                                true /* complete */,
-                                EuiccService.RESULT_OK,
-                                0 /* resolvableError */,
-                                "whatever" /* callingPackage */));
+        callDownloadSubscription(SUBSCRIPTION, false /* switchAfterDownload */, true /* complete */,
+                12345, 0 /* resolvableError */, "whatever" /* callingPackage */);
+
+        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, 0 /* detailedCode */);
         assertFalse(mController.mCalledRefreshSubscriptionsAndSendResult);
     }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java
index 17a428b..4db0bc9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmMmiCodeTest.java
@@ -20,12 +20,15 @@
 
 import static junit.framework.Assert.fail;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
 import android.os.AsyncResult;
 import android.os.PersistableBundle;
 import android.telephony.CarrierConfigManager;
@@ -332,6 +335,44 @@
                 eq(com.android.internal.R.string.mmiErrorNotSupported));
     }
 
+    @Test
+    public void testFacCode() {
+        // Put a valid FAC code into the carrier config.
+        CarrierConfigManager ccm = (CarrierConfigManager) mGsmCdmaPhoneUT.getContext()
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        PersistableBundle cc = ccm.getConfigForSubId(0);
+        cc.putStringArray(CarrierConfigManager.KEY_FEATURE_ACCESS_CODES_STRING_ARRAY,
+                new String[] {"112"});
+
+        // Try using a dial string with the FAC; this should result in a NULL GsmMmiCode.
+        GsmMmiCode validFac = GsmMmiCode.newFromDialString("#112#6505551212*", mGsmCdmaPhoneUT,
+                null, null);
+        assertNull(validFac);
+
+        // Try using a dial string with the FAC; this should result in a NULL GsmMmiCode.
+        // Note this case is somewhat contrived, however the GsmMmiCode parsing does allow non-digit
+        // characters, so we will here too.
+        GsmMmiCode validFac2 = GsmMmiCode.newFromDialString("#112#650-555-1212*", mGsmCdmaPhoneUT,
+                null, null);
+        assertNull(validFac2);
+
+        // Try using a dial string with a different made up FAC; this one is not in the carrier
+        // config so should just return an MMI code.
+        GsmMmiCode invalidFac = GsmMmiCode.newFromDialString("#113#6505551212*", mGsmCdmaPhoneUT,
+                null, null);
+        assertNotNull(invalidFac);
+
+        // Now try the carrier config FAC code, but it's formatted as if it a USSD.
+        GsmMmiCode ussd = GsmMmiCode.newFromDialString("*#112*6505551212#", mGsmCdmaPhoneUT,
+                null, null);
+        assertNotNull(ussd);
+
+        // Now try the carrier config FAC code, but it's not a valid FAC formatted string
+        GsmMmiCode invalidFormat = GsmMmiCode.newFromDialString("*#112*6505551212", mGsmCdmaPhoneUT,
+                null, null);
+        assertNull(invalidFormat);
+    }
+
     private void setCarrierSupportsCallerIdVerticalServiceCodesCarrierConfig() {
         final PersistableBundle bundle = new PersistableBundle();
         bundle.putBoolean(CarrierConfigManager
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
index 7e65048..4abf33f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
@@ -62,6 +62,7 @@
 
 import com.android.ims.ImsFeatureBinderRepository;
 import com.android.internal.telephony.PhoneConfigurationManager;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -113,6 +114,7 @@
     private BroadcastReceiver mTestBootCompleteReceiver;
     private ImsServiceFeatureQueryManager.Listener mDynamicQueryListener;
     private PersistableBundle[] mCarrierConfigs;
+    private FeatureFlags mFeatureFlags;
 
     @Before
     @Override
@@ -127,6 +129,7 @@
         mMockQueryManagerFactory = mock(ImsResolver.ImsDynamicQueryManagerFactory.class);
         mMockQueryManager = mock(ImsServiceFeatureQueryManager.class);
         mMockRepo = mock(ImsFeatureBinderRepository.class);
+        mFeatureFlags = mock(FeatureFlags.class);
     }
 
     @After
@@ -1969,7 +1972,7 @@
         }
 
         mTestImsResolver = new ImsResolver(mMockContext, deviceMmTelPkgName, deviceRcsPkgName,
-                numSlots, mMockRepo, Looper.myLooper());
+                numSlots, mMockRepo, Looper.myLooper(), mFeatureFlags);
 
         mTestImsResolver.setSubscriptionManagerProxy(mTestSubscriptionManagerProxy);
         mTestImsResolver.setTelephonyManagerProxy(mTestTelephonyManagerProxy);
@@ -2008,7 +2011,7 @@
                     @Override
                     public ImsServiceController create(Context context, ComponentName componentName,
                             ImsServiceController.ImsServiceControllerCallbacks callbacks,
-                            ImsFeatureBinderRepository r) {
+                            ImsFeatureBinderRepository r, FeatureFlags featureFlags) {
                         when(controller.getComponentName()).thenReturn(componentName);
                         return controller;
                     }
@@ -2118,7 +2121,7 @@
                     @Override
                     public ImsServiceController create(Context context, ComponentName componentName,
                             ImsServiceController.ImsServiceControllerCallbacks callbacks,
-                            ImsFeatureBinderRepository r) {
+                            ImsFeatureBinderRepository r, FeatureFlags featureFlags) {
                         return controllerMap.get(componentName.getPackageName());
                     }
                 });
@@ -2136,7 +2139,7 @@
                     @Override
                     public ImsServiceController create(Context context, ComponentName componentName,
                             ImsServiceController.ImsServiceControllerCallbacks callbacks,
-                            ImsFeatureBinderRepository r) {
+                            ImsFeatureBinderRepository r, FeatureFlags featureFlags) {
                         if (TEST_DEVICE_DEFAULT_NAME.getPackageName().equals(
                                 componentName.getPackageName())) {
                             when(deviceController.getComponentName()).thenReturn(componentName);
@@ -2163,7 +2166,7 @@
                     @Override
                     public ImsServiceController create(Context context, ComponentName componentName,
                             ImsServiceController.ImsServiceControllerCallbacks callbacks,
-                            ImsFeatureBinderRepository r) {
+                            ImsFeatureBinderRepository r, FeatureFlags featureFlags) {
                         if (TEST_DEVICE_DEFAULT_NAME.getPackageName().equals(
                                 componentName.getPackageName())) {
                             when(deviceController.getComponentName()).thenReturn(componentName);
@@ -2195,7 +2198,7 @@
                     @Override
                     public ImsServiceController create(Context context, ComponentName componentName,
                             ImsServiceController.ImsServiceControllerCallbacks callbacks,
-                            ImsFeatureBinderRepository r) {
+                            ImsFeatureBinderRepository r, FeatureFlags featureFlags) {
                         if (TEST_DEVICE_DEFAULT_NAME.getPackageName().equals(
                                 componentName.getPackageName())) {
                             when(deviceController1.getComponentName()).thenReturn(componentName);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
index 0b6aa9f..65b73fb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
@@ -57,6 +57,7 @@
 import com.android.ims.ImsFeatureContainer;
 import com.android.ims.internal.IImsFeatureStatusCallback;
 import com.android.ims.internal.IImsServiceFeatureCallback;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -126,6 +127,7 @@
     IImsRegistration mMockRcsRegistration;
     IImsServiceController mMockServiceControllerBinder;
     ImsServiceController.ImsServiceControllerCallbacks mMockCallbacks;
+    FeatureFlags mFeatureFlags;
     Context mMockContext;
 
     private final ComponentName mTestComponentName = new ComponentName("TestPkg",
@@ -146,11 +148,12 @@
         mMockRcsRegistration = mock(IImsRegistration.class);
         mMockServiceControllerBinder = mock(IImsServiceController.class);
         mMockCallbacks = mock(ImsServiceController.ImsServiceControllerCallbacks.class);
+        mFeatureFlags = mock(FeatureFlags.class);
         mMockContext = mock(Context.class);
 
         mRepo = new ImsFeatureBinderRepository();
         mTestImsServiceController = new ImsServiceController(mMockContext, mTestComponentName,
-                mMockCallbacks, mHandler, REBIND_RETRY, mRepo);
+                mMockCallbacks, mHandler, REBIND_RETRY, mRepo, mFeatureFlags);
         when(mMockContext.bindService(any(), any(), anyInt())).thenReturn(true);
         when(mMockServiceControllerBinder.createMmTelFeature(anyInt(), anyInt()))
                 .thenReturn(mMockMmTelFeature);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
index 6c4493b..58eca5d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneConnectionTest.java
@@ -40,6 +40,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
@@ -57,6 +59,7 @@
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.Connection;
 import com.android.internal.telephony.GsmCdmaCall;
+import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.imsphone.ImsPhone.ImsDialArgs;
@@ -95,6 +98,8 @@
         mForeGroundCall = mock(ImsPhoneCall.class);
         mBackGroundCall = mock(ImsPhoneCall.class);
         mRingGroundCall = mock(ImsPhoneCall.class);
+        mTelephonyManager = mock(TelephonyManager.class);
+        mCarrierConfigManager = mock(CarrierConfigManager.class);
         replaceInstance(Handler.class, "mLooper", mImsCT, Looper.myLooper());
         replaceInstance(ImsPhoneCallTracker.class, "mForegroundCall", mImsCT, mForeGroundCall);
         replaceInstance(ImsPhoneCallTracker.class, "mBackgroundCall", mImsCT, mBackGroundCall);
@@ -103,6 +108,10 @@
 
         mImsCallProfile.mCallExtras = mBundle;
         doReturn(ImsPhoneCall.State.IDLE).when(mForeGroundCall).getState();
+
+        // By default, turn off the business composer
+        setUserEnabledBusinessComposer(false);
+        setCarrierConfigBusinessComposer(false);
     }
 
     @After
@@ -431,6 +440,97 @@
                 ImsPhoneConnection.toTelecomVerificationStatus(90210));
     }
 
+    /**
+     * Assert the helper method
+     * {@link ImsPhoneConnection#isBusinessOnlyCallComposerEnabledByUser(Phone)} is Working As
+     * Intended.
+     */
+    @Test
+    @SmallTest
+    public void testIsBusinessOnlyCallComposerEnabledByUser() {
+        mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
+        assertFalse(mConnectionUT.isBusinessOnlyCallComposerEnabledByUser(mImsPhone));
+        setUserEnabledBusinessComposer(true);
+        assertTrue(mConnectionUT.isBusinessOnlyCallComposerEnabledByUser(mImsPhone));
+        setUserEnabledBusinessComposer(false);
+        assertFalse(mConnectionUT.isBusinessOnlyCallComposerEnabledByUser(mImsPhone));
+    }
+
+    /**
+     * Assert the helper method
+     * {@link ImsPhoneConnection#isBusinessComposerEnabledByConfig(Phone)} is Working As
+     * Intended.
+     */
+    @Test
+    @SmallTest
+    public void testBusinessComposerEnabledByCarrierConfig() {
+        mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
+        assertFalse(mConnectionUT.isBusinessComposerEnabledByConfig(mImsPhone));
+        setCarrierConfigBusinessComposer(true);
+        assertTrue(mConnectionUT.isBusinessComposerEnabledByConfig(mImsPhone));
+        setCarrierConfigBusinessComposer(false);
+        assertFalse(mConnectionUT.isBusinessComposerEnabledByConfig(mImsPhone));
+    }
+
+    /**
+     * Verify that the {@link ImsPhoneConnection#getIsBusinessComposerFeatureEnabled()} only
+     * returns true when it is enabled by the CarrierConfigManager and user.
+     */
+    @Test
+    @SmallTest
+    public void testIncomingImsCallSetsTheBusinessComposerFeatureStatus() {
+        mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
+        assertFalse(mConnectionUT.getIsBusinessComposerFeatureEnabled());
+
+        setUserEnabledBusinessComposer(true);
+        setCarrierConfigBusinessComposer(false);
+        mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
+        assertFalse(mConnectionUT.getIsBusinessComposerFeatureEnabled());
+
+        setUserEnabledBusinessComposer(false);
+        setCarrierConfigBusinessComposer(true);
+        mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
+        assertFalse(mConnectionUT.getIsBusinessComposerFeatureEnabled());
+
+        setUserEnabledBusinessComposer(true);
+        setCarrierConfigBusinessComposer(true);
+        mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
+        assertTrue(mConnectionUT.getIsBusinessComposerFeatureEnabled());
+    }
+
+    /**
+     * If the business composer feature is off but ImsCallProfile extras still injected by the lower
+     * layer, Telephony should NOT inject the telecom call extras.
+     */
+    @Test
+    @SmallTest
+    public void testMaybeInjectBusinessExtrasWithFeatureOff() {
+        setUserEnabledBusinessComposer(false);
+        setCarrierConfigBusinessComposer(false);
+        mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
+        assertFalse(mConnectionUT.getIsBusinessComposerFeatureEnabled());
+        Bundle businessExtras = getBusinessExtras();
+        mConnectionUT.maybeInjectBusinessComposerExtras(businessExtras);
+        assertFalse(businessExtras.containsKey(android.telecom.Call.EXTRA_IS_BUSINESS_CALL));
+        assertFalse(businessExtras.containsKey(android.telecom.Call.EXTRA_ASSERTED_DISPLAY_NAME));
+    }
+
+    /**
+     * Verify if the business composer feature is on, telephony is injecting the telecom call extras
+     */
+    @Test
+    @SmallTest
+    public void testMaybeInjectBusinessExtrasWithFeatureOn() {
+        setUserEnabledBusinessComposer(true);
+        setCarrierConfigBusinessComposer(true);
+        mConnectionUT = new ImsPhoneConnection(mImsPhone, mImsCall, mImsCT, mForeGroundCall, false);
+        assertTrue(mConnectionUT.getIsBusinessComposerFeatureEnabled());
+        Bundle businessExtras = getBusinessExtras();
+        mConnectionUT.maybeInjectBusinessComposerExtras(businessExtras);
+        assertTrue(businessExtras.containsKey(android.telecom.Call.EXTRA_IS_BUSINESS_CALL));
+        assertTrue(businessExtras.containsKey(android.telecom.Call.EXTRA_ASSERTED_DISPLAY_NAME));
+    }
+
     @Test
     @SmallTest
     public void testSetRedirectingAddress() {
@@ -481,4 +581,32 @@
         latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         assertTrue(receivedCountCallback[0]);
     }
+
+    private void setUserEnabledBusinessComposer(boolean isEnabled) {
+        when(mPhone.getContext()).thenReturn(mContext);
+        when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+        if (isEnabled) {
+            when(mTelephonyManager.getCallComposerStatus()).thenReturn(
+                    TelephonyManager.CALL_COMPOSER_STATUS_BUSINESS_ONLY);
+        } else {
+            when(mTelephonyManager.getCallComposerStatus()).thenReturn(
+                    TelephonyManager.CALL_COMPOSER_STATUS_OFF);
+        }
+    }
+
+    private void setCarrierConfigBusinessComposer(boolean isEnabled) {
+        when(mPhone.getContext()).thenReturn(mContext);
+        when(mContext.getSystemService(CarrierConfigManager.class)).thenReturn(
+                mCarrierConfigManager);
+        PersistableBundle b = new PersistableBundle();
+        b.putBoolean(CarrierConfigManager.KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, isEnabled);
+        when(mCarrierConfigManager.getConfigForSubId(mPhone.getSubId())).thenReturn(b);
+    }
+
+    private Bundle getBusinessExtras() {
+        Bundle businessExtras = new Bundle();
+        businessExtras.putBoolean(ImsCallProfile.EXTRA_IS_BUSINESS_CALL, true);
+        businessExtras.putString(ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME, "Google");
+        return businessExtras;
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
index 14cff4b..992f50a 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java
@@ -73,6 +73,7 @@
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.sysprop.TelephonyProperties;
+import android.telecom.VideoProfile;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionInfo;
@@ -284,6 +285,14 @@
         doReturn(Call.State.INCOMING).when(mRingingCall).getState();
         assertEquals(true, mImsPhoneUT.handleInCallMmiCommands("2"));
         verify(mImsCT).acceptCall(ImsCallProfile.CALL_TYPE_VOICE);
+
+        // Verify b/286499659, fixed media type
+        doReturn(true).when(mFeatureFlags).answerAudioOnlyWhenAnsweringViaMmiCode();
+        doReturn(Call.State.IDLE).when(mForegroundCall).getState();
+        doReturn(Call.State.IDLE).when(mBackgroundCall).getState();
+        doReturn(Call.State.INCOMING).when(mRingingCall).getState();
+        assertEquals(true, mImsPhoneUT.handleInCallMmiCommands("2"));
+        verify(mImsCT).acceptCall(VideoProfile.STATE_AUDIO_ONLY);
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/DataCallSessionStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/DataCallSessionStatsTest.java
index d63dc3c..7374aef 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/DataCallSessionStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/DataCallSessionStatsTest.java
@@ -19,7 +19,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -32,11 +34,13 @@
 import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataCallResponse;
-import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
+import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
 
 import org.junit.After;
 import org.junit.Before;
@@ -94,7 +98,7 @@
     @Test
     @SmallTest
     public void testSetupDataCallOnCellularIms_success() {
-        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS, false);
         mDataCallSessionStats.onSetupDataCallResponse(
                 mDefaultImsResponse,
                 TelephonyManager.NETWORK_TYPE_LTE,
@@ -120,7 +124,7 @@
     @Test
     @SmallTest
     public void testSetupDataCallOnIwlan_success() {
-        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS, false);
         mDataCallSessionStats.onSetupDataCallResponse(
                 mDefaultImsResponse,
                 TelephonyManager.NETWORK_TYPE_IWLAN,
@@ -149,7 +153,7 @@
     public void testSetupDataCallOnCrossSimCalling_success() {
         doReturn(mCellularNetworkCapabilities)
                 .when(mDefaultNetworkMonitor).getNetworkCapabilities();
-        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS, false);
         mDataCallSessionStats.onSetupDataCallResponse(
                 mDefaultImsResponse,
                 TelephonyManager.NETWORK_TYPE_IWLAN,
@@ -176,7 +180,7 @@
     @Test
     @SmallTest
     public void testSetupDataCallOnCellularIms_failure() {
-        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS, false);
         mDataCallSessionStats.onSetupDataCallResponse(
                 mDefaultImsResponse,
                 TelephonyManager.NETWORK_TYPE_LTE,
@@ -199,7 +203,7 @@
     @Test
     @SmallTest
     public void testHandoverFromCellularToIwlan_success() {
-        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS, false);
         mDataCallSessionStats.onSetupDataCallResponse(
                 mDefaultImsResponse,
                 TelephonyManager.NETWORK_TYPE_LTE,
@@ -225,7 +229,7 @@
     @Test
     @SmallTest
     public void testHandoverFromCellularToCrossSimCalling_success() {
-        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS, false);
         mDataCallSessionStats.onSetupDataCallResponse(
                 mDefaultImsResponse,
                 TelephonyManager.NETWORK_TYPE_LTE,
@@ -254,7 +258,7 @@
     @Test
     @SmallTest
     public void testHandoverFromCellularToIwlan_failure() {
-        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS, false);
         mDataCallSessionStats.onSetupDataCallResponse(
                 mDefaultImsResponse,
                 TelephonyManager.NETWORK_TYPE_LTE,
@@ -286,7 +290,7 @@
     @Test
     @SmallTest
     public void testSetupDataCallOnIwlan_success_thenOOS() {
-        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS, false);
         mDataCallSessionStats.onSetupDataCallResponse(
                 mDefaultImsResponse,
                 TelephonyManager.NETWORK_TYPE_IWLAN,
@@ -308,4 +312,100 @@
         assertTrue(stats.oosAtEnd);
         assertFalse(stats.ongoing);
     }
+
+    @Test
+    public void testIsNtn() {
+        when(mSatelliteController.isInSatelliteModeForCarrierRoaming(any())).thenReturn(true);
+
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS, false);
+        mDataCallSessionStats.onSetupDataCallResponse(
+                mDefaultImsResponse,
+                TelephonyManager.NETWORK_TYPE_LTE,
+                ApnSetting.TYPE_IMS,
+                ApnSetting.PROTOCOL_IP,
+                DataFailCause.NONE);
+
+        mDataCallSessionStats.setTimeMillis(60000L);
+        mDataCallSessionStats.conclude();
+
+        ArgumentCaptor<DataCallSession> callCaptor =
+                ArgumentCaptor.forClass(DataCallSession.class);
+        verify(mPersistAtomsStorage).addDataCallSession(callCaptor.capture());
+        DataCallSession stats = callCaptor.getValue();
+
+        assertTrue(stats.isNtn);
+
+        reset(mPersistAtomsStorage);
+
+        when(mSatelliteController.isInSatelliteModeForCarrierRoaming(any()))
+                .thenReturn(false);
+
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS, false);
+        mDataCallSessionStats.onSetupDataCallResponse(
+                mDefaultImsResponse,
+                TelephonyManager.NETWORK_TYPE_LTE,
+                ApnSetting.TYPE_IMS,
+                ApnSetting.PROTOCOL_IP,
+                DataFailCause.NONE);
+
+        mDataCallSessionStats.setTimeMillis(60000L);
+        mDataCallSessionStats.conclude();
+
+
+        verify(mPersistAtomsStorage).addDataCallSession(callCaptor.capture());
+        stats = callCaptor.getValue();
+
+        assertFalse(stats.isNtn);
+    }
+
+    @Test
+    public void testIsProvisioningProfile() {
+        SubscriptionInfoInternal mSubInfoInternal = new SubscriptionInfoInternal.Builder()
+            .setProfileClass(mSubscriptionManager.PROFILE_CLASS_PROVISIONING).build();
+
+        when(mSubscriptionManagerService.getSubscriptionInfoInternal(mPhone.getSubId()))
+            .thenReturn(mSubInfoInternal);
+
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS, false);
+        mDataCallSessionStats.onSetupDataCallResponse(
+            mDefaultImsResponse,
+            TelephonyManager.NETWORK_TYPE_IWLAN,
+            ApnSetting.TYPE_IMS,
+            ApnSetting.PROTOCOL_IP,
+            DataFailCause.NONE);
+
+        mDataCallSessionStats.setTimeMillis(60000L);
+        mDataCallSessionStats.conclude();
+
+        ArgumentCaptor<DataCallSession> callCaptor =
+            ArgumentCaptor.forClass(DataCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addDataCallSession(
+            callCaptor.capture());
+        DataCallSession stats = callCaptor.getValue();
+
+        assertTrue(stats.isProvisioningProfile);
+
+        reset(mPersistAtomsStorage);
+
+        mSubInfoInternal = new SubscriptionInfoInternal.Builder()
+            .setProfileClass(mSubscriptionManager.PROFILE_CLASS_OPERATIONAL).build();
+
+        when(mSubscriptionManagerService.getSubscriptionInfoInternal(mPhone.getSubId()))
+            .thenReturn(mSubInfoInternal);
+
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS, false);
+        mDataCallSessionStats.onSetupDataCallResponse(
+            mDefaultImsResponse,
+            TelephonyManager.NETWORK_TYPE_IWLAN,
+            ApnSetting.TYPE_IMS,
+            ApnSetting.PROTOCOL_IP,
+            DataFailCause.NONE);
+
+        mDataCallSessionStats.setTimeMillis(60000L);
+        mDataCallSessionStats.conclude();
+        verify(mPersistAtomsStorage).addDataCallSession(callCaptor.capture());
+        stats = callCaptor.getValue();
+
+        assertFalse(stats.isProvisioningProfile);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/DataNetworkValidationStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/DataNetworkValidationStatsTest.java
new file mode 100644
index 0000000..b9493b9
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/DataNetworkValidationStatsTest.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.metrics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.telephony.PreciseDataConnectionState;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+import android.telephony.data.ApnSetting;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyStatsLog;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.nano.PersistAtomsProto;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+/** Test DataNetworkValidationStats */
+public class DataNetworkValidationStatsTest extends TelephonyTest {
+
+    /** Initial time at starting tests */
+    private static final Long STARTED_TIME_IN_MILLIS = 5000000L;
+
+    private TestableDataNetworkValidationStats mDataNetworkValidationStats;
+
+    /** Test Class for override elapsed time */
+    private static class TestableDataNetworkValidationStats extends DataNetworkValidationStats {
+        private long mSystemClockInMillis;
+        TestableDataNetworkValidationStats(Phone phone) {
+            super(phone);
+            mSystemClockInMillis = STARTED_TIME_IN_MILLIS;
+        }
+
+        @Override
+        protected long getTimeMillis() {
+            return mSystemClockInMillis;
+        }
+
+        public void elapseTimeInMillis(long elapse) {
+            mSystemClockInMillis += elapse;
+        }
+    }
+
+    @Before
+    public void setup() throws Exception {
+        super.setUp(getClass().getSimpleName());
+
+        mDataNetworkValidationStats = new TestableDataNetworkValidationStats(mPhone);
+        doReturn(SignalStrength.SIGNAL_STRENGTH_GREAT).when(mSignalStrength).getLevel();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mDataNetworkValidationStats = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void testRequestDataNetworkValidation() {
+        mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_IMS);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void testOnUpdateNetworkValidationStateWithSuccessStatus() {
+
+        // Test
+        mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_IMS);
+        mDataNetworkValidationStats.elapseTimeInMillis(100L);
+        mDataNetworkValidationStats.onUpdateNetworkValidationState(
+                PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        // Verify that atom was logged
+        ArgumentCaptor<PersistAtomsProto.DataNetworkValidation> captor =
+                ArgumentCaptor.forClass(PersistAtomsProto.DataNetworkValidation.class);
+        verify(mPersistAtomsStorage, times(1)).addDataNetworkValidation(
+                captor.capture());
+        PersistAtomsProto.DataNetworkValidation proto = captor.getValue();
+
+        // Make sure variables
+        assertEquals(
+                TelephonyStatsLog.DATA_NETWORK_VALIDATION__NETWORK_TYPE__NETWORK_TYPE_LTE,
+                proto.networkType);
+        assertEquals(ApnSetting.TYPE_IMS, proto.apnTypeBitmask);
+        assertEquals(
+                TelephonyStatsLog.DATA_NETWORK_VALIDATION__SIGNAL_STRENGTH__SIGNAL_STRENGTH_GREAT,
+                proto.signalStrength);
+        assertEquals(TelephonyStatsLog
+                        .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_SUCCESS,
+                proto.validationResult);
+        assertEquals(100L, proto.elapsedTimeInMillis);
+        assertFalse(proto.handoverAttempted);
+        assertEquals(1, proto.networkValidationCount);
+    }
+
+    @Test
+    public void testOnUpdateNetworkValidationStateWithFailureStatus() {
+
+        // Test
+        mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_EMERGENCY);
+        mDataNetworkValidationStats.elapseTimeInMillis(100L);
+        mDataNetworkValidationStats.onHandoverAttempted();
+        mDataNetworkValidationStats.elapseTimeInMillis(100L);
+        mDataNetworkValidationStats.onUpdateNetworkValidationState(
+                PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS,
+                TelephonyManager.NETWORK_TYPE_IWLAN);
+
+        // Verify that atom was logged
+        ArgumentCaptor<PersistAtomsProto.DataNetworkValidation> captor =
+                ArgumentCaptor.forClass(PersistAtomsProto.DataNetworkValidation.class);
+        verify(mPersistAtomsStorage, times(1)).addDataNetworkValidation(
+                captor.capture());
+        PersistAtomsProto.DataNetworkValidation proto = captor.getValue();
+
+        // Make sure variables
+        assertEquals(
+                TelephonyStatsLog.DATA_NETWORK_VALIDATION__NETWORK_TYPE__NETWORK_TYPE_IWLAN,
+                proto.networkType);
+        assertEquals(ApnSetting.TYPE_EMERGENCY, proto.apnTypeBitmask);
+        assertEquals(
+                TelephonyStatsLog.DATA_NETWORK_VALIDATION__SIGNAL_STRENGTH__SIGNAL_STRENGTH_GREAT,
+                proto.signalStrength);
+        assertEquals(TelephonyStatsLog
+                        .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_SUCCESS,
+                proto.validationResult);
+        assertEquals(200L, proto.elapsedTimeInMillis);
+        assertTrue(proto.handoverAttempted);
+        assertEquals(1, proto.networkValidationCount);
+    }
+
+    @Test
+    public void testOnUpdateNetworkValidationStateWithInProgressStatus() {
+
+        // Test
+        mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_IMS);
+        mDataNetworkValidationStats.elapseTimeInMillis(100L);
+        mDataNetworkValidationStats.onUpdateNetworkValidationState(
+                PreciseDataConnectionState.NETWORK_VALIDATION_NOT_REQUESTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        // Verify
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void testOnUpdateNetworkValidationStateWithNotRequestedStatus() {
+
+        // Test
+        mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_IMS);
+        mDataNetworkValidationStats.elapseTimeInMillis(100L);
+        mDataNetworkValidationStats.onUpdateNetworkValidationState(
+                PreciseDataConnectionState.NETWORK_VALIDATION_NOT_REQUESTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        // Verify
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void testOnUpdateNetworkValidationStateWithUnsupportedStatus() {
+
+        // Set up
+        doReturn(SignalStrength.SIGNAL_STRENGTH_POOR).when(mSignalStrength).getLevel();
+
+        // Test
+        mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_IMS);
+        mDataNetworkValidationStats.elapseTimeInMillis(300L);
+        mDataNetworkValidationStats.onUpdateNetworkValidationState(
+                PreciseDataConnectionState.NETWORK_VALIDATION_UNSUPPORTED,
+                TelephonyManager.NETWORK_TYPE_LTE);
+
+        // Verify that atom was logged
+        ArgumentCaptor<PersistAtomsProto.DataNetworkValidation> captor =
+                ArgumentCaptor.forClass(PersistAtomsProto.DataNetworkValidation.class);
+        verify(mPersistAtomsStorage, times(1)).addDataNetworkValidation(
+                captor.capture());
+        PersistAtomsProto.DataNetworkValidation proto = captor.getValue();
+
+        // Make sure variables
+        assertEquals(
+                TelephonyStatsLog.DATA_NETWORK_VALIDATION__NETWORK_TYPE__NETWORK_TYPE_LTE,
+                proto.networkType);
+        assertEquals(ApnSetting.TYPE_IMS, proto.apnTypeBitmask);
+        assertEquals(
+                TelephonyStatsLog.DATA_NETWORK_VALIDATION__SIGNAL_STRENGTH__SIGNAL_STRENGTH_POOR,
+                proto.signalStrength);
+        assertEquals(TelephonyStatsLog
+                .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_NOT_SUPPORTED,
+                proto.validationResult);
+        assertEquals(300L, proto.elapsedTimeInMillis);
+        assertFalse(proto.handoverAttempted);
+        assertEquals(1, proto.networkValidationCount);
+    }
+
+
+    @Test
+    public void testOnDataNetworkDisconnected() {
+
+        // Set up
+        doReturn(SignalStrength.SIGNAL_STRENGTH_POOR).when(mSignalStrength).getLevel();
+
+        // Test
+        mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_IMS);
+        mDataNetworkValidationStats.elapseTimeInMillis(300L);
+        mDataNetworkValidationStats.onDataNetworkDisconnected(TelephonyManager.NETWORK_TYPE_LTE);
+
+        // Verify that atom was logged
+        ArgumentCaptor<PersistAtomsProto.DataNetworkValidation> captor =
+                ArgumentCaptor.forClass(PersistAtomsProto.DataNetworkValidation.class);
+        verify(mPersistAtomsStorage, times(1)).addDataNetworkValidation(
+                captor.capture());
+        PersistAtomsProto.DataNetworkValidation proto = captor.getValue();
+
+        // Make sure variables
+        assertEquals(
+                TelephonyStatsLog.DATA_NETWORK_VALIDATION__NETWORK_TYPE__NETWORK_TYPE_LTE,
+                proto.networkType);
+        assertEquals(ApnSetting.TYPE_IMS, proto.apnTypeBitmask);
+        assertEquals(
+                TelephonyStatsLog.DATA_NETWORK_VALIDATION__SIGNAL_STRENGTH__SIGNAL_STRENGTH_POOR,
+                proto.signalStrength);
+        assertEquals(TelephonyStatsLog
+                .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_UNSPECIFIED,
+                proto.validationResult);
+        assertEquals(300L, proto.elapsedTimeInMillis);
+        assertFalse(proto.handoverAttempted);
+        assertEquals(1, proto.networkValidationCount);
+    }
+
+    @Test
+    public void testOnUpdateNetworkValidationState_DupStatus() {
+
+        // Test
+        mDataNetworkValidationStats.onRequestNetworkValidation(ApnSetting.TYPE_IMS);
+        mDataNetworkValidationStats.elapseTimeInMillis(100L);
+        mDataNetworkValidationStats.onUpdateNetworkValidationState(
+                PreciseDataConnectionState.NETWORK_VALIDATION_IN_PROGRESS,
+                TelephonyManager.NETWORK_TYPE_LTE);
+        mDataNetworkValidationStats.elapseTimeInMillis(100L);
+        mDataNetworkValidationStats.onUpdateNetworkValidationState(
+                PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS,
+                TelephonyManager.NETWORK_TYPE_UMTS);
+        mDataNetworkValidationStats.elapseTimeInMillis(100L);
+        mDataNetworkValidationStats.onUpdateNetworkValidationState(
+                PreciseDataConnectionState.NETWORK_VALIDATION_FAILURE,
+                TelephonyManager.NETWORK_TYPE_NR);
+
+        // Verify that atom was logged
+        ArgumentCaptor<PersistAtomsProto.DataNetworkValidation> captor =
+                ArgumentCaptor.forClass(PersistAtomsProto.DataNetworkValidation.class);
+        verify(mPersistAtomsStorage, times(1)).addDataNetworkValidation(
+                captor.capture());
+        PersistAtomsProto.DataNetworkValidation proto = captor.getValue();
+
+        // Make sure variables
+        assertEquals(
+                TelephonyStatsLog.DATA_NETWORK_VALIDATION__NETWORK_TYPE__NETWORK_TYPE_UMTS,
+                proto.networkType);
+        assertEquals(ApnSetting.TYPE_IMS, proto.apnTypeBitmask);
+        assertEquals(
+                TelephonyStatsLog.DATA_NETWORK_VALIDATION__SIGNAL_STRENGTH__SIGNAL_STRENGTH_GREAT,
+                proto.signalStrength);
+        assertEquals(TelephonyStatsLog
+                        .DATA_NETWORK_VALIDATION__VALIDATION_RESULT__VALIDATION_RESULT_SUCCESS,
+                proto.validationResult);
+        assertEquals(200L, proto.elapsedTimeInMillis);
+        assertFalse(proto.handoverAttempted);
+        assertEquals(1, proto.networkValidationCount);
+    }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
index 0426737..04b45b3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/MetricsCollectorTest.java
@@ -16,9 +16,13 @@
 
 package com.android.internal.telephony.metrics;
 
+import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS;
+import static com.android.internal.telephony.TelephonyStatsLog.CARRIER_ROAMING_SATELLITE_SESSION;
 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_DATA_SERVICE_SWITCH;
 import static com.android.internal.telephony.TelephonyStatsLog.CELLULAR_SERVICE_STATE;
 import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SHORT_CODE_SMS;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_CONFIG_UPDATER;
+import static com.android.internal.telephony.TelephonyStatsLog.SATELLITE_ENTITLEMENT;
 import static com.android.internal.telephony.TelephonyStatsLog.SIM_SLOT_STATE;
 import static com.android.internal.telephony.TelephonyStatsLog.SUPPORTED_RADIO_ACCESS_FAMILY;
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_RAT_USAGE;
@@ -45,9 +49,13 @@
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyTest;
 import com.android.internal.telephony.flags.FeatureFlags;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteControllerStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteSession;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteConfigUpdater;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallRatUsage;
 import com.android.internal.telephony.nano.PersistAtomsProto.VoiceCallSession;
 import com.android.internal.telephony.uicc.IccCardStatus.CardState;
@@ -70,8 +78,8 @@
                     .setCoolDownMillis(24L * 3600L * 1000L)
                     .build();
     private static final long MIN_COOLDOWN_MILLIS = 23L * 3600L * 1000L;
-    private static final long CELL_SERVICE_MIN_COOLDOWN_MILLIS =
-            IS_DEBUGGABLE ? 4L *  60L * 1000L : MIN_COOLDOWN_MILLIS;
+    private static final long POWER_CORRELATED_MIN_COOLDOWN_MILLIS =
+            IS_DEBUGGABLE ? 4L *  60L * 1000L : 5L * 3600L * 1000L;
     private static final long MIN_CALLS_PER_BUCKET = 5L;
 
     // NOTE: these fields are currently 32-bit internally and padded to 64-bit by TelephonyManager
@@ -115,7 +123,7 @@
         mFeatureFlags = mock(FeatureFlags.class);
         mMetricsCollector =
                 new MetricsCollector(mContext, mPersistAtomsStorage,
-                        mDeviceStateHelper, mVonrHelper, mFeatureFlags);
+                        mDeviceStateHelper, mVonrHelper, mDefaultNetworkMonitor, mFeatureFlags);
         doReturn(mSST).when(mSecondPhone).getServiceStateTracker();
         doReturn(mServiceStateStats).when(mSST).getServiceStateStats();
     }
@@ -402,6 +410,9 @@
     @SmallTest
     public void onPullAtom_cellularServiceState_tooFrequent() throws Exception {
         doReturn(null).when(mPersistAtomsStorage).getCellularServiceStates(anyLong());
+        mContextFixture.putIntResource(
+                com.android.internal.R.integer.config_metrics_pull_cooldown_millis,
+                (int) POWER_CORRELATED_MIN_COOLDOWN_MILLIS);
         List<StatsEvent> actualAtoms = new ArrayList<>();
 
         int result = mMetricsCollector.onPullAtom(CELLULAR_SERVICE_STATE, actualAtoms);
@@ -409,7 +420,7 @@
         assertThat(actualAtoms).hasSize(0);
         assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
         verify(mPersistAtomsStorage, times(1)).getCellularServiceStates(
-                eq(CELL_SERVICE_MIN_COOLDOWN_MILLIS));
+                eq(POWER_CORRELATED_MIN_COOLDOWN_MILLIS));
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -469,4 +480,178 @@
         assertThat(actualAtoms).hasSize(4);
         assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
     }
+
+    @Test
+    public void onPullAtom_carrierRoamingSatelliteSession_empty() {
+        doReturn(new CarrierRoamingSatelliteSession[0]).when(mPersistAtomsStorage)
+                .getCarrierRoamingSatelliteSessionStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(CARRIER_ROAMING_SATELLITE_SESSION, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_carrierRoamingSatelliteSession_tooFrequent() {
+        doReturn(null).when(mPersistAtomsStorage)
+                .getCarrierRoamingSatelliteSessionStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(CARRIER_ROAMING_SATELLITE_SESSION, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
+        verify(mPersistAtomsStorage, times(1))
+                .getCarrierRoamingSatelliteSessionStats(eq(MIN_COOLDOWN_MILLIS));
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onPullAtom_carrierRoamingSatelliteSession_multipleAtoms() {
+        CarrierRoamingSatelliteSession carrierRoamingSatelliteSession =
+                new CarrierRoamingSatelliteSession();
+        doReturn(new CarrierRoamingSatelliteSession[] {carrierRoamingSatelliteSession,
+                carrierRoamingSatelliteSession, carrierRoamingSatelliteSession,
+                carrierRoamingSatelliteSession})
+                .when(mPersistAtomsStorage)
+                .getCarrierRoamingSatelliteSessionStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(CARRIER_ROAMING_SATELLITE_SESSION, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(4);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_carrierRoamingSatelliteControllerStats_empty() {
+        doReturn(new CarrierRoamingSatelliteControllerStats[0]).when(mPersistAtomsStorage)
+                .getCarrierRoamingSatelliteControllerStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS,
+                actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_carrierRoamingSatelliteControllerStats_multipleAtoms() {
+        CarrierRoamingSatelliteControllerStats carrierRoamingSatelliteControllerStats =
+                new CarrierRoamingSatelliteControllerStats();
+        doReturn(new CarrierRoamingSatelliteControllerStats[] {
+                carrierRoamingSatelliteControllerStats})
+                .when(mPersistAtomsStorage)
+                .getCarrierRoamingSatelliteControllerStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS,
+                actualAtoms);
+
+        assertThat(actualAtoms).hasSize(1);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_carrierRoamingSatelliteControllerStats_tooFrequent() {
+        doReturn(null).when(mPersistAtomsStorage)
+                .getCarrierRoamingSatelliteControllerStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(CARRIER_ROAMING_SATELLITE_CONTROLLER_STATS,
+                actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
+        verify(mPersistAtomsStorage, times(1))
+                .getCarrierRoamingSatelliteControllerStats(eq(MIN_COOLDOWN_MILLIS));
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onPullAtom_satelliteEntitlement_empty() {
+        doReturn(new SatelliteEntitlement[0]).when(mPersistAtomsStorage)
+                .getSatelliteEntitlementStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SATELLITE_ENTITLEMENT, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_satelliteEntitlement_tooFrequent() {
+        doReturn(null).when(mPersistAtomsStorage).getSatelliteEntitlementStats(
+                anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SATELLITE_ENTITLEMENT, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
+        verify(mPersistAtomsStorage, times(1))
+                .getSatelliteEntitlementStats(eq(MIN_COOLDOWN_MILLIS));
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onPullAtom_satelliteEntitlement_multipleAtoms() {
+        SatelliteEntitlement satelliteEntitlement = new SatelliteEntitlement();
+        doReturn(new SatelliteEntitlement[] {satelliteEntitlement, satelliteEntitlement,
+                satelliteEntitlement, satelliteEntitlement})
+                .when(mPersistAtomsStorage)
+                .getSatelliteEntitlementStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SATELLITE_ENTITLEMENT, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(4);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_satelliteConfigUpdater_empty() {
+        doReturn(new SatelliteConfigUpdater[0]).when(mPersistAtomsStorage)
+                .getSatelliteConfigUpdaterStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SATELLITE_CONFIG_UPDATER, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
+
+    @Test
+    public void onPullAtom_satelliteConfigUpdater_tooFrequent() {
+        doReturn(null).when(mPersistAtomsStorage).getSatelliteConfigUpdaterStats(
+                anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SATELLITE_CONFIG_UPDATER, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(0);
+        assertThat(result).isEqualTo(StatsManager.PULL_SKIP);
+        verify(mPersistAtomsStorage, times(1))
+                .getSatelliteConfigUpdaterStats(eq(MIN_COOLDOWN_MILLIS));
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onPullAtom_satelliteConfigUpdater_multipleAtoms() {
+        SatelliteConfigUpdater satelliteConfigUpdater = new SatelliteConfigUpdater();
+        doReturn(new SatelliteConfigUpdater[] {satelliteConfigUpdater, satelliteConfigUpdater,
+                satelliteConfigUpdater, satelliteConfigUpdater})
+                .when(mPersistAtomsStorage)
+                .getSatelliteConfigUpdaterStats(anyLong());
+        List<StatsEvent> actualAtoms = new ArrayList<>();
+
+        int result = mMetricsCollector.onPullAtom(SATELLITE_CONFIG_UPDATER, actualAtoms);
+
+        assertThat(actualAtoms).hasSize(4);
+        assertThat(result).isEqualTo(StatsManager.PULL_SUCCESS);
+    }
 }
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 475c90b..8ee3a37 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
@@ -16,13 +16,27 @@
 
 package com.android.internal.telephony.metrics;
 
+import static android.telephony.SmsManager.RESULT_ERROR_NONE;
+import static android.telephony.SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_BITMASK_GPRS;
 import static android.telephony.TelephonyManager.NETWORK_TYPE_BITMASK_GSM;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 
+import static com.android.internal.telephony.SmsResponse.NO_ERROR_CODE;
 import static com.android.internal.telephony.TelephonyStatsLog.GBA_EVENT__FAILED_REASON__FEATURE_NOT_READY;
 import static com.android.internal.telephony.TelephonyStatsLog.GBA_EVENT__FAILED_REASON__UNKNOWN;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_ERROR_GENERIC;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__ERROR__SMS_SUCCESS;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP2;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP2;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TYPE__SMS_TYPE_NORMAL;
+import static com.android.internal.telephony.TelephonyStatsLog.INCOMING_SMS__SMS_TYPE__SMS_TYPE_SMS_PP;
+import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR;
+import static com.android.internal.telephony.TelephonyStatsLog.OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_SUCCESS;
 import static com.android.internal.telephony.TelephonyStatsLog.RCS_ACS_PROVISIONING_STATS__RESPONSE_TYPE__ERROR;
 import static com.android.internal.telephony.TelephonyStatsLog.RCS_ACS_PROVISIONING_STATS__RESPONSE_TYPE__PROVISIONING_XML;
 import static com.android.internal.telephony.TelephonyStatsLog.RCS_CLIENT_PROVISIONING_STATS__EVENT__CLIENT_PARAMS_SENT;
@@ -31,6 +45,10 @@
 import static com.android.internal.telephony.TelephonyStatsLog.VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS;
 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.satellite.SatelliteConstants.ACCESS_CONTROL_TYPE_CURRENT_LOCATION;
+import static com.android.internal.telephony.satellite.SatelliteConstants.ACCESS_CONTROL_TYPE_NETWORK_COUNTRY_CODE;
+import static com.android.internal.telephony.satellite.SatelliteConstants.CONFIG_DATA_SOURCE_CONFIG_UPDATER;
+import static com.android.internal.telephony.satellite.SatelliteConstants.CONFIG_DATA_SOURCE_DEVICE_CONFIG;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -49,10 +67,13 @@
 import android.content.Context;
 import android.os.Build;
 import android.telephony.DisconnectCause;
+import android.telephony.PreciseDataConnectionState;
 import android.telephony.SatelliteProtoEnums;
 import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyProtoEnums;
+import android.telephony.data.ApnSetting;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.SipDelegateManager;
 
@@ -60,9 +81,12 @@
 
 import com.android.internal.telephony.TelephonyStatsLog;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteControllerStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteSession;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularDataServiceSwitch;
 import com.android.internal.telephony.nano.PersistAtomsProto.CellularServiceState;
 import com.android.internal.telephony.nano.PersistAtomsProto.DataCallSession;
+import com.android.internal.telephony.nano.PersistAtomsProto.DataNetworkValidation;
 import com.android.internal.telephony.nano.PersistAtomsProto.GbaEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsDedicatedBearerListenerEvent;
@@ -70,12 +94,17 @@
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationServiceDescStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.ImsRegistrationTermination;
+import com.android.internal.telephony.nano.PersistAtomsProto.IncomingSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingShortCodeSms;
+import com.android.internal.telephony.nano.PersistAtomsProto.OutgoingSms;
 import com.android.internal.telephony.nano.PersistAtomsProto.PersistAtoms;
 import com.android.internal.telephony.nano.PersistAtomsProto.PresenceNotifyEvent;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsAcsProvisioningStats;
 import com.android.internal.telephony.nano.PersistAtomsProto.RcsClientProvisioningStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteAccessController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
@@ -106,6 +135,7 @@
 import java.util.Comparator;
 import java.util.LinkedList;
 import java.util.Queue;
+import java.util.concurrent.TimeUnit;
 
 public class PersistAtomsStorageTest extends TelephonyTest {
     private static final String TEST_FILE = "PersistAtomsStorageTest.pb";
@@ -235,6 +265,14 @@
     private SipTransportSession mSipTransportSession2;
     private SipTransportSession[] mSipTransportSession;
 
+    private IncomingSms mIncomingSms1;
+    private IncomingSms mIncomingSms2;
+    private IncomingSms[] mIncomingSms;
+
+    private OutgoingSms mOutgoingSms1;
+    private OutgoingSms mOutgoingSms2;
+    private OutgoingSms[] mOutgoingSms;
+
     private OutgoingShortCodeSms mOutgoingShortCodeSms1;
     private OutgoingShortCodeSms mOutgoingShortCodeSms2;
     private OutgoingShortCodeSms[] mOutgoingShortCodeSms;
@@ -263,6 +301,32 @@
     private SatelliteSosMessageRecommender mSatelliteSosMessageRecommender2;
     private SatelliteSosMessageRecommender[] mSatelliteSosMessageRecommenders;
 
+    private DataNetworkValidation mDataNetworkValidationLte1;
+    private DataNetworkValidation mDataNetworkValidationLte2;
+    private DataNetworkValidation mDataNetworkValidationIwlan1;
+    private DataNetworkValidation mDataNetworkValidationIwlan2;
+    private DataNetworkValidation[] mDataNetworkValidations;
+
+    private CarrierRoamingSatelliteSession mCarrierRoamingSatelliteSession1;
+    private CarrierRoamingSatelliteSession mCarrierRoamingSatelliteSession2;
+    private CarrierRoamingSatelliteSession[] mCarrierRoamingSatelliteSessions;
+
+    private CarrierRoamingSatelliteControllerStats mCarrierRoamingSatelliteControllerStats1;
+    private CarrierRoamingSatelliteControllerStats mCarrierRoamingSatelliteControllerStats2;
+    private CarrierRoamingSatelliteControllerStats[] mCarrierRoamingSatelliteControllerStats;
+
+    private SatelliteEntitlement mSatelliteEntitlement1;
+    private SatelliteEntitlement mSatelliteEntitlement2;
+    private SatelliteEntitlement[] mSatelliteEntitlements;
+
+    private SatelliteConfigUpdater mSatelliteConfigUpdater1;
+    private SatelliteConfigUpdater mSatelliteConfigUpdater2;
+    private SatelliteConfigUpdater[] mSatelliteConfigUpdaters;
+
+    private SatelliteAccessController mSatelliteAccessController1;
+    private SatelliteAccessController mSatelliteAccessController2;
+    private SatelliteAccessController[] mSatelliteAccessControllers;
+
     private void makeTestData() {
         // MO call with SRVCC (LTE to UMTS)
         mCall1Proto = new VoiceCallSession();
@@ -946,6 +1010,97 @@
         mSipTransportSession =
                 new SipTransportSession[] {mSipTransportSession1, mSipTransportSession2};
 
+        generateTestSmsData();
+        generateTestSatelliteData();
+    }
+
+    private void generateTestSmsData() {
+        mIncomingSms1 = new IncomingSms();
+        mIncomingSms1.smsFormat = INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP;
+        mIncomingSms1.smsTech = INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP;
+        mIncomingSms1.rat = TelephonyManager.NETWORK_TYPE_LTE;
+        mIncomingSms1.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_NORMAL;
+        mIncomingSms1.totalParts = 1;
+        mIncomingSms1.receivedParts = 1;
+        mIncomingSms1.blocked = false;
+        mIncomingSms1.error = INCOMING_SMS__ERROR__SMS_SUCCESS;
+        mIncomingSms1.isRoaming = false;
+        mIncomingSms1.simSlotIndex = 0;
+        mIncomingSms1.isMultiSim = false;
+        mIncomingSms1.isEsim = false;
+        mIncomingSms1.carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+        mIncomingSms1.messageId = 0;
+        mIncomingSms1.count = 1;
+        mIncomingSms1.isManagedProfile = false;
+        mIncomingSms1.isNtn = false;
+        mIncomingSms1.isEmergency = true;
+
+        mIncomingSms2 = new IncomingSms();
+        mIncomingSms2.smsFormat = INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP2;
+        mIncomingSms2.smsTech = INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP2;
+        mIncomingSms2.rat = TelephonyManager.NETWORK_TYPE_CDMA;
+        mIncomingSms2.smsType = INCOMING_SMS__SMS_TYPE__SMS_TYPE_SMS_PP;
+        mIncomingSms2.totalParts = 2;
+        mIncomingSms2.receivedParts = 2;
+        mIncomingSms2.blocked = true;
+        mIncomingSms2.error = INCOMING_SMS__ERROR__SMS_ERROR_GENERIC;
+        mIncomingSms2.isRoaming = true;
+        mIncomingSms2.simSlotIndex = 1;
+        mIncomingSms2.isMultiSim = true;
+        mIncomingSms2.isEsim = true;
+        mIncomingSms2.carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+        mIncomingSms2.messageId = 1;
+        mIncomingSms2.count = 2;
+        mIncomingSms2.isManagedProfile = true;
+        mIncomingSms2.isNtn = true;
+        mIncomingSms2.isEmergency = true;
+
+        mIncomingSms = new IncomingSms[] {mIncomingSms1, mIncomingSms2};
+
+        mOutgoingSms1 = new OutgoingSms();
+        mOutgoingSms1.smsFormat = INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP;
+        mOutgoingSms1.smsTech = INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP;
+        mOutgoingSms1.rat = TelephonyManager.NETWORK_TYPE_LTE;
+        mOutgoingSms1.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_SUCCESS;
+        mOutgoingSms1.errorCode = NO_ERROR_CODE;
+        mOutgoingSms1.isRoaming = false;
+        mOutgoingSms1.isFromDefaultApp = true;
+        mOutgoingSms1.simSlotIndex = 0;
+        mOutgoingSms1.isMultiSim = false;
+        mOutgoingSms1.carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+        mOutgoingSms1.messageId = 0;
+        mOutgoingSms1.retryId = 0;
+        mOutgoingSms1.intervalMillis = 0;
+        mOutgoingSms1.count = 1;
+        mOutgoingSms1.sendErrorCode = RESULT_ERROR_NONE;
+        mOutgoingSms1.networkErrorCode = NO_ERROR_CODE;
+        mOutgoingSms1.isManagedProfile = false;
+        mOutgoingSms1.isEmergency = false;
+        mOutgoingSms1.isNtn = false;
+
+        mOutgoingSms2 = new OutgoingSms();
+        mOutgoingSms2.smsFormat = INCOMING_SMS__SMS_FORMAT__SMS_FORMAT_3GPP2;
+        mOutgoingSms2.smsTech = INCOMING_SMS__SMS_TECH__SMS_TECH_CS_3GPP2;
+        mOutgoingSms2.rat = TelephonyManager.NETWORK_TYPE_CDMA;
+        mOutgoingSms2.sendResult = OUTGOING_SMS__SEND_RESULT__SMS_SEND_RESULT_ERROR;
+        mOutgoingSms2.errorCode = NO_ERROR_CODE;
+        mOutgoingSms2.isRoaming = true;
+        mOutgoingSms2.isFromDefaultApp = false;
+        mOutgoingSms2.simSlotIndex = 1;
+        mOutgoingSms2.isMultiSim = true;
+        mOutgoingSms2.carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
+        mOutgoingSms2.messageId = 1;
+        mOutgoingSms2.retryId = 1;
+        mOutgoingSms2.intervalMillis = 10;
+        mOutgoingSms2.count = 2;
+        mOutgoingSms2.sendErrorCode = RESULT_RIL_SMS_SEND_FAIL_RETRY;
+        mOutgoingSms2.networkErrorCode = NO_ERROR_CODE;
+        mOutgoingSms2.isManagedProfile = true;
+        mOutgoingSms2.isEmergency = true;
+        mOutgoingSms2.isNtn = true;
+
+        mOutgoingSms = new OutgoingSms[] {mOutgoingSms1, mOutgoingSms2};
+
         mOutgoingShortCodeSms1 = new OutgoingShortCodeSms();
         mOutgoingShortCodeSms1.category = 1;
         mOutgoingShortCodeSms1.xmlVersion = 30;
@@ -960,6 +1115,8 @@
                 mOutgoingShortCodeSms2};
 
         generateTestSatelliteData();
+
+        generateTestDataNetworkValidationsData();
     }
 
     private void generateTestSatelliteData() {
@@ -981,6 +1138,14 @@
         mSatelliteController1.totalServiceUptimeSec = 60 * 60 * 24 * 7;
         mSatelliteController1.totalBatteryConsumptionPercent = 7;
         mSatelliteController1.totalBatteryChargedTimeSec = 60 * 60 * 3 * 1;
+        mSatelliteController1.countOfDemoModeSatelliteServiceEnablementsSuccess = 3;
+        mSatelliteController1.countOfDemoModeSatelliteServiceEnablementsFail = 1;
+        mSatelliteController1.countOfDemoModeOutgoingDatagramSuccess = 4;
+        mSatelliteController1.countOfDemoModeOutgoingDatagramFail = 2;
+        mSatelliteController1.countOfDemoModeIncomingDatagramSuccess = 3;
+        mSatelliteController1.countOfDemoModeIncomingDatagramFail = 2;
+        mSatelliteController1.countOfDatagramTypeKeepAliveSuccess = 1;
+        mSatelliteController1.countOfDatagramTypeKeepAliveFail = 2;
 
         mSatelliteController2 = new SatelliteController();
         mSatelliteController2.countOfSatelliteServiceEnablementsSuccess = 2 + 1;
@@ -999,7 +1164,15 @@
         mSatelliteController2.countOfDeprovisionFail = 16;
         mSatelliteController2.totalServiceUptimeSec = 60 * 60 * 12;
         mSatelliteController2.totalBatteryConsumptionPercent = 14;
-        mSatelliteController1.totalBatteryChargedTimeSec = 60 * 60 * 3;
+        mSatelliteController2.totalBatteryChargedTimeSec = 60 * 60 * 3;
+        mSatelliteController2.countOfDemoModeSatelliteServiceEnablementsSuccess = 5;
+        mSatelliteController2.countOfDemoModeSatelliteServiceEnablementsFail = 4;
+        mSatelliteController2.countOfDemoModeOutgoingDatagramSuccess = 3;
+        mSatelliteController2.countOfDemoModeOutgoingDatagramFail = 7;
+        mSatelliteController2.countOfDemoModeIncomingDatagramSuccess = 2;
+        mSatelliteController2.countOfDemoModeIncomingDatagramFail = 3;
+        mSatelliteController2.countOfDatagramTypeKeepAliveSuccess = 4;
+        mSatelliteController2.countOfDatagramTypeKeepAliveFail = 5;
 
         // SatelliteController atom has one data point
         mSatelliteControllers =
@@ -1013,6 +1186,17 @@
         mSatelliteSession1.satelliteTechnology =
                 SatelliteProtoEnums.NT_RADIO_TECHNOLOGY_PROPRIETARY;
         mSatelliteSession1.count = 1;
+        mSatelliteSession1.satelliteServiceTerminationResult =
+                SatelliteProtoEnums.SATELLITE_ERROR_NONE;
+        mSatelliteSession1.initializationProcessingTimeMillis = 100;
+        mSatelliteSession1.terminationProcessingTimeMillis = 200;
+        mSatelliteSession1.sessionDurationSeconds = 3;
+        mSatelliteSession1.countOfOutgoingDatagramSuccess = 1;
+        mSatelliteSession1.countOfOutgoingDatagramFailed = 0;
+        mSatelliteSession1.countOfIncomingDatagramSuccess = 1;
+        mSatelliteSession1.countOfIncomingDatagramFailed = 0;
+        mSatelliteSession1.isDemoMode = false;
+        mSatelliteSession1.maxNtnSignalStrengthLevel = 2;
 
         mSatelliteSession2 = new SatelliteSession();
         mSatelliteSession2.satelliteServiceInitializationResult =
@@ -1020,6 +1204,17 @@
         mSatelliteSession2.satelliteTechnology =
                 SatelliteProtoEnums.NT_RADIO_TECHNOLOGY_NB_IOT_NTN;
         mSatelliteSession2.count = 1;
+        mSatelliteSession2.satelliteServiceTerminationResult =
+                SatelliteProtoEnums.SATELLITE_ERROR_NONE;
+        mSatelliteSession2.initializationProcessingTimeMillis = 300;
+        mSatelliteSession2.terminationProcessingTimeMillis = 100;
+        mSatelliteSession2.sessionDurationSeconds = 10;
+        mSatelliteSession2.countOfOutgoingDatagramSuccess = 0;
+        mSatelliteSession2.countOfOutgoingDatagramFailed = 2;
+        mSatelliteSession2.countOfIncomingDatagramSuccess = 0;
+        mSatelliteSession2.countOfIncomingDatagramFailed = 1;
+        mSatelliteSession2.isDemoMode = true;
+        mSatelliteSession2.maxNtnSignalStrengthLevel = 4;
 
         mSatelliteSessions =
                 new SatelliteSession[] {
@@ -1030,11 +1225,13 @@
         mSatelliteIncomingDatagram1.resultCode = SatelliteProtoEnums.SATELLITE_ERROR_NONE;
         mSatelliteIncomingDatagram1.datagramSizeBytes = 1 * 1024;
         mSatelliteIncomingDatagram1.datagramTransferTimeMillis = 3 * 1000;
+        mSatelliteIncomingDatagram1.isDemoMode = false;
 
         mSatelliteIncomingDatagram2 = new SatelliteIncomingDatagram();
         mSatelliteIncomingDatagram2.resultCode = SatelliteProtoEnums.SATELLITE_MODEM_ERROR;
         mSatelliteIncomingDatagram2.datagramSizeBytes = 512;
         mSatelliteIncomingDatagram2.datagramTransferTimeMillis = 1 * 1000;
+        mSatelliteIncomingDatagram1.isDemoMode = true;
 
         mSatelliteIncomingDatagrams =
                 new SatelliteIncomingDatagram[] {
@@ -1047,6 +1244,7 @@
         mSatelliteOutgoingDatagram1.resultCode = SatelliteProtoEnums.SATELLITE_ERROR_NONE;
         mSatelliteOutgoingDatagram1.datagramSizeBytes = 1 * 1024;
         mSatelliteOutgoingDatagram1.datagramTransferTimeMillis = 3 * 1000;
+        mSatelliteOutgoingDatagram1.isDemoMode = false;
 
         mSatelliteOutgoingDatagram2 = new SatelliteOutgoingDatagram();
         mSatelliteOutgoingDatagram2.datagramType =
@@ -1054,6 +1252,7 @@
         mSatelliteOutgoingDatagram2.resultCode = SatelliteProtoEnums.SATELLITE_MODEM_ERROR;
         mSatelliteOutgoingDatagram2.datagramSizeBytes = 512;
         mSatelliteOutgoingDatagram2.datagramTransferTimeMillis = 1 * 1000;
+        mSatelliteOutgoingDatagram1.isDemoMode = true;
 
         mSatelliteOutgoingDatagrams =
                 new SatelliteOutgoingDatagram[] {
@@ -1104,6 +1303,187 @@
                 new SatelliteSosMessageRecommender[] {
                         mSatelliteSosMessageRecommender1, mSatelliteSosMessageRecommender2
                 };
+
+        mCarrierRoamingSatelliteSession1 = new CarrierRoamingSatelliteSession();
+        mCarrierRoamingSatelliteSession1.carrierId = 1;
+        mCarrierRoamingSatelliteSession1.isNtnRoamingInHomeCountry = false;
+        mCarrierRoamingSatelliteSession1.totalSatelliteModeTimeSec = 60;
+        mCarrierRoamingSatelliteSession1.numberOfSatelliteConnections = 3;
+        mCarrierRoamingSatelliteSession1.avgDurationOfSatelliteConnectionSec = 20;
+        mCarrierRoamingSatelliteSession1.satelliteConnectionGapMinSec = 2;
+        mCarrierRoamingSatelliteSession1.satelliteConnectionGapAvgSec = 5;
+        mCarrierRoamingSatelliteSession1.satelliteConnectionGapMaxSec = 8;
+        mCarrierRoamingSatelliteSession1.rsrpAvg = 3;
+        mCarrierRoamingSatelliteSession1.rsrpMedian = 2;
+        mCarrierRoamingSatelliteSession1.rssnrAvg = 5;
+        mCarrierRoamingSatelliteSession1.rssnrMedian = 3;
+        mCarrierRoamingSatelliteSession1.countOfIncomingSms = 2;
+        mCarrierRoamingSatelliteSession1.countOfOutgoingSms = 4;
+        mCarrierRoamingSatelliteSession1.countOfIncomingMms = 1;
+        mCarrierRoamingSatelliteSession1.countOfOutgoingMms = 1;
+
+        mCarrierRoamingSatelliteSession2 = new CarrierRoamingSatelliteSession();
+        mCarrierRoamingSatelliteSession2.carrierId = 2;
+        mCarrierRoamingSatelliteSession2.isNtnRoamingInHomeCountry = true;
+        mCarrierRoamingSatelliteSession2.totalSatelliteModeTimeSec = 120;
+        mCarrierRoamingSatelliteSession2.numberOfSatelliteConnections = 5;
+        mCarrierRoamingSatelliteSession2.avgDurationOfSatelliteConnectionSec = 20;
+        mCarrierRoamingSatelliteSession2.satelliteConnectionGapMinSec = 2;
+        mCarrierRoamingSatelliteSession2.satelliteConnectionGapAvgSec = 5;
+        mCarrierRoamingSatelliteSession2.satelliteConnectionGapMaxSec = 8;
+        mCarrierRoamingSatelliteSession2.rsrpAvg = 3;
+        mCarrierRoamingSatelliteSession2.rsrpMedian = 2;
+        mCarrierRoamingSatelliteSession2.rssnrAvg = 8;
+        mCarrierRoamingSatelliteSession2.rssnrMedian = 15;
+        mCarrierRoamingSatelliteSession2.countOfIncomingSms = 2;
+        mCarrierRoamingSatelliteSession2.countOfOutgoingSms = 4;
+        mCarrierRoamingSatelliteSession2.countOfIncomingMms = 1;
+        mCarrierRoamingSatelliteSession2.countOfOutgoingMms = 1;
+
+        mCarrierRoamingSatelliteSessions = new CarrierRoamingSatelliteSession[] {
+                mCarrierRoamingSatelliteSession1, mCarrierRoamingSatelliteSession2};
+
+        mCarrierRoamingSatelliteControllerStats1 = new CarrierRoamingSatelliteControllerStats();
+        mCarrierRoamingSatelliteControllerStats1.configDataSource =
+                SatelliteProtoEnums.CONFIG_DATA_SOURCE_ENTITLEMENT;
+        mCarrierRoamingSatelliteControllerStats1.countOfEntitlementStatusQueryRequest = 2;
+        mCarrierRoamingSatelliteControllerStats1.countOfSatelliteConfigUpdateRequest = 1;
+        mCarrierRoamingSatelliteControllerStats1.countOfSatelliteNotificationDisplayed = 1;
+        mCarrierRoamingSatelliteControllerStats1.satelliteSessionGapMinSec = 2;
+        mCarrierRoamingSatelliteControllerStats1.satelliteSessionGapAvgSec = 3;
+        mCarrierRoamingSatelliteControllerStats1.satelliteSessionGapMaxSec = 4;
+
+        mCarrierRoamingSatelliteControllerStats2 = new CarrierRoamingSatelliteControllerStats();
+        mCarrierRoamingSatelliteControllerStats2.configDataSource =
+                SatelliteProtoEnums.CONFIG_DATA_SOURCE_CONFIG_UPDATER;
+        mCarrierRoamingSatelliteControllerStats2.countOfEntitlementStatusQueryRequest = 4;
+        mCarrierRoamingSatelliteControllerStats2.countOfSatelliteConfigUpdateRequest = 1;
+        mCarrierRoamingSatelliteControllerStats2.countOfSatelliteNotificationDisplayed = 1;
+        mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapMinSec = 5;
+        mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapAvgSec = 10;
+        mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapMaxSec = 15;
+
+        // CarrierRoamingSatelliteController has one data point
+        mCarrierRoamingSatelliteControllerStats = new CarrierRoamingSatelliteControllerStats[] {
+                mCarrierRoamingSatelliteControllerStats1};
+
+        mSatelliteEntitlement1 = new SatelliteEntitlement();
+        mSatelliteEntitlement1.carrierId = 1;
+        mSatelliteEntitlement1.result = 0;
+        mSatelliteEntitlement1.entitlementStatus =
+                SatelliteProtoEnums.SATELLITE_ENTITLEMENT_STATUS_ENABLED;
+        mSatelliteEntitlement1.isRetry = false;
+        mSatelliteEntitlement1.count = 1;
+
+        mSatelliteEntitlement2 = new SatelliteEntitlement();
+        mSatelliteEntitlement2.carrierId = 2;
+        mSatelliteEntitlement2.result = 1;
+        mSatelliteEntitlement2.entitlementStatus =
+                SatelliteProtoEnums.SATELLITE_ENTITLEMENT_STATUS_DISABLED;
+        mSatelliteEntitlement1.isRetry = true;
+        mSatelliteEntitlement2.count = 1;
+
+        mSatelliteEntitlements = new SatelliteEntitlement[] {mSatelliteEntitlement1,
+                mSatelliteEntitlement2};
+
+        mSatelliteConfigUpdater1 = new SatelliteConfigUpdater();
+        mSatelliteConfigUpdater1.configVersion = 1;
+        mSatelliteConfigUpdater1.oemConfigResult = SatelliteProtoEnums.CONFIG_UPDATE_RESULT_SUCCESS;
+        mSatelliteConfigUpdater1.carrierConfigResult =
+                SatelliteProtoEnums.CONFIG_UPDATE_RESULT_CARRIER_DATA_INVALID_PLMN;
+        mSatelliteConfigUpdater1.count = 1;
+
+        mSatelliteConfigUpdater2 = new SatelliteConfigUpdater();
+        mSatelliteConfigUpdater2.configVersion = 2;
+        mSatelliteConfigUpdater2.oemConfigResult =
+                SatelliteProtoEnums.CONFIG_UPDATE_RESULT_DEVICE_DATA_INVALID_COUNTRY_CODE;
+        mSatelliteConfigUpdater2.carrierConfigResult =
+                SatelliteProtoEnums.CONFIG_UPDATE_RESULT_SUCCESS;
+        mSatelliteConfigUpdater2.count = 1;
+
+        mSatelliteConfigUpdaters = new SatelliteConfigUpdater[] {mSatelliteConfigUpdater1,
+                mSatelliteConfigUpdater2};
+
+        mSatelliteAccessController1 = new SatelliteAccessController();
+        mSatelliteAccessController1.accessControlType = ACCESS_CONTROL_TYPE_NETWORK_COUNTRY_CODE;
+        mSatelliteAccessController1.locationQueryTimeMillis = TimeUnit.SECONDS.toMillis(1);
+        mSatelliteAccessController1.onDeviceLookupTimeMillis = TimeUnit.SECONDS.toMillis(2);
+        mSatelliteAccessController1.totalCheckingTimeMillis = TimeUnit.SECONDS.toMillis(3);
+        mSatelliteAccessController1.isAllowed = true;
+        mSatelliteAccessController1.isEmergency = false;
+        mSatelliteAccessController1.resultCode = SATELLITE_RESULT_SUCCESS;
+        mSatelliteAccessController1.countryCodes = new String[]{"AB", "CD"};
+        mSatelliteAccessController1.configDataSource = CONFIG_DATA_SOURCE_DEVICE_CONFIG;
+
+        mSatelliteAccessController2 = new SatelliteAccessController();
+        mSatelliteAccessController1.accessControlType = ACCESS_CONTROL_TYPE_CURRENT_LOCATION;
+        mSatelliteAccessController1.locationQueryTimeMillis = TimeUnit.SECONDS.toMillis(4);
+        mSatelliteAccessController2.onDeviceLookupTimeMillis = TimeUnit.SECONDS.toMillis(5);
+        mSatelliteAccessController2.totalCheckingTimeMillis = TimeUnit.SECONDS.toMillis(6);
+        mSatelliteAccessController2.isAllowed = false;
+        mSatelliteAccessController2.isEmergency = true;
+        mSatelliteAccessController2.resultCode = SATELLITE_RESULT_SUCCESS;
+        mSatelliteAccessController2.countryCodes = new String[]{"EF", "GH"};
+        mSatelliteAccessController2.configDataSource = CONFIG_DATA_SOURCE_CONFIG_UPDATER;
+
+        mSatelliteAccessControllers = new SatelliteAccessController[]{
+                mSatelliteAccessController1, mSatelliteAccessController2
+        };
+    }
+
+    private void generateTestDataNetworkValidationsData() {
+
+        // for LTE #1
+        mDataNetworkValidationLte1 = new DataNetworkValidation();
+        mDataNetworkValidationLte1.networkType = TelephonyManager.NETWORK_TYPE_LTE;
+        mDataNetworkValidationLte1.apnTypeBitmask = ApnSetting.TYPE_IMS;
+        mDataNetworkValidationLte1.signalStrength = SignalStrength.SIGNAL_STRENGTH_GREAT;
+        mDataNetworkValidationLte1.validationResult =
+                PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS;
+        mDataNetworkValidationLte1.elapsedTimeInMillis = 100L;
+        mDataNetworkValidationLte1.handoverAttempted = false;
+        mDataNetworkValidationLte1.networkValidationCount = 1;
+
+        // for LTE #2
+        mDataNetworkValidationLte2 = new DataNetworkValidation();
+        mDataNetworkValidationLte2.networkType = TelephonyManager.NETWORK_TYPE_LTE;
+        mDataNetworkValidationLte2.apnTypeBitmask = ApnSetting.TYPE_IMS;
+        mDataNetworkValidationLte2.signalStrength = SignalStrength.SIGNAL_STRENGTH_GREAT;
+        mDataNetworkValidationLte2.validationResult =
+                PreciseDataConnectionState.NETWORK_VALIDATION_SUCCESS;
+        mDataNetworkValidationLte2.elapsedTimeInMillis = 100L;
+        mDataNetworkValidationLte2.handoverAttempted = false;
+        mDataNetworkValidationLte2.networkValidationCount = 1;
+
+        // for IWLAN #1
+        mDataNetworkValidationIwlan1 = new DataNetworkValidation();
+        mDataNetworkValidationIwlan1.networkType = TelephonyManager.NETWORK_TYPE_IWLAN;
+        mDataNetworkValidationIwlan1.apnTypeBitmask = ApnSetting.TYPE_IMS;
+        mDataNetworkValidationIwlan1.signalStrength = SignalStrength.SIGNAL_STRENGTH_POOR;
+        mDataNetworkValidationIwlan1.validationResult =
+                PreciseDataConnectionState.NETWORK_VALIDATION_FAILURE;
+        mDataNetworkValidationIwlan1.elapsedTimeInMillis = 10000L;
+        mDataNetworkValidationIwlan1.handoverAttempted = false;
+        mDataNetworkValidationIwlan1.networkValidationCount = 1;
+
+        // for IWLAN #2
+        mDataNetworkValidationIwlan2 = new DataNetworkValidation();
+        mDataNetworkValidationIwlan2.networkType = TelephonyManager.NETWORK_TYPE_IWLAN;
+        mDataNetworkValidationIwlan2.apnTypeBitmask = ApnSetting.TYPE_IMS;
+        mDataNetworkValidationIwlan2.signalStrength = SignalStrength.SIGNAL_STRENGTH_POOR;
+        mDataNetworkValidationIwlan2.validationResult =
+                PreciseDataConnectionState.NETWORK_VALIDATION_FAILURE;
+        mDataNetworkValidationIwlan2.elapsedTimeInMillis = 30000L;
+        mDataNetworkValidationIwlan2.handoverAttempted = false;
+        mDataNetworkValidationIwlan2.networkValidationCount = 1;
+
+        mDataNetworkValidations =
+                new DataNetworkValidation[] {
+                        mDataNetworkValidationLte1,
+                        mDataNetworkValidationLte1,
+                        mDataNetworkValidationIwlan1,
+                        mDataNetworkValidationIwlan2,
+                };
     }
 
     private static class TestablePersistAtomsStorage extends PersistAtomsStorage {
@@ -1244,6 +1624,12 @@
         mSipTransportSession1 = null;
         mSipTransportSession2 = null;
         mSipTransportSession = null;
+        mIncomingSms = null;
+        mIncomingSms1 = null;
+        mIncomingSms2 = null;
+        mOutgoingSms = null;
+        mOutgoingSms1 = null;
+        mOutgoingSms2 = null;
         mOutgoingShortCodeSms1 = null;
         mOutgoingShortCodeSms2 = null;
         mOutgoingShortCodeSms = null;
@@ -1265,6 +1651,25 @@
         mSatelliteSosMessageRecommender1 = null;
         mSatelliteSosMessageRecommender2 = null;
         mSatelliteSosMessageRecommenders = null;
+        mDataNetworkValidationLte1 = null;
+        mDataNetworkValidationLte2 = null;
+        mDataNetworkValidationIwlan1 = null;
+        mDataNetworkValidationIwlan2 = null;
+        mCarrierRoamingSatelliteSession1 = null;
+        mCarrierRoamingSatelliteSession2 = null;
+        mCarrierRoamingSatelliteSessions = null;
+        mCarrierRoamingSatelliteControllerStats1 = null;
+        mCarrierRoamingSatelliteControllerStats2 = null;
+        mCarrierRoamingSatelliteControllerStats = null;
+        mSatelliteEntitlement1 = null;
+        mSatelliteEntitlement2 = null;
+        mSatelliteEntitlements = null;
+        mSatelliteConfigUpdater1 = null;
+        mSatelliteConfigUpdater2 = null;
+        mSatelliteConfigUpdaters = null;
+        mSatelliteAccessController1 = null;
+        mSatelliteAccessController2 = null;
+        mSatelliteAccessControllers = null;
         super.tearDown();
     }
 
@@ -3721,6 +4126,88 @@
     }
 
     @Test
+    public void addIncomingSms_emptyProto() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addIncomingSms(mIncomingSms1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // IncomingSms should be added successfully, changes should be saved.
+        verifyCurrentStateSavedToFileOnce();
+        IncomingSms[] expectedList = new IncomingSms[] {mIncomingSms1};
+        assertProtoArrayEquals(expectedList, mPersistAtomsStorage.getIncomingSms(0L));
+    }
+
+    @Test
+    public void addIncomingSms_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addIncomingSms(mIncomingSms1);
+        mPersistAtomsStorage.addIncomingSms(mIncomingSms2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // IncomingSms should be added successfully.
+        verifyCurrentStateSavedToFileOnce();
+        IncomingSms[] expectedList = new IncomingSms[] {mIncomingSms1, mIncomingSms2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList, mPersistAtomsStorage.getIncomingSms(0L));
+    }
+
+    @Test
+    public void getIncomingSms_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        // Pull interval less than minimum.
+        mPersistAtomsStorage.incTimeMillis(50L);
+        IncomingSms[] outgoingShortCodeSmsList = mPersistAtomsStorage.getIncomingSms(100L);
+        // Should be denied.
+        assertNull(outgoingShortCodeSmsList);
+    }
+
+    @Test
+    public void addOutgoingSms_emptyProto() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addOutgoingSms(mOutgoingSms1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // OutgoingSms should be added successfully, changes should be saved.
+        verifyCurrentStateSavedToFileOnce();
+        OutgoingSms[] expectedList = new OutgoingSms[] {mOutgoingSms1};
+        assertProtoArrayEquals(expectedList, mPersistAtomsStorage.getOutgoingSms(0L));
+    }
+
+    @Test
+    public void addOutgoingSms_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addOutgoingSms(mOutgoingSms1);
+        mPersistAtomsStorage.addOutgoingSms(mOutgoingSms2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // OutgoingSms should be added successfully.
+        verifyCurrentStateSavedToFileOnce();
+        OutgoingSms[] expectedList = new OutgoingSms[] {mOutgoingSms1, mOutgoingSms2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList, mPersistAtomsStorage.getOutgoingSms(0L));
+    }
+
+    @Test
+    public void getOutgoingSms_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        // Pull interval less than minimum.
+        mPersistAtomsStorage.incTimeMillis(50L);
+        OutgoingSms[] outgoingShortCodeSmsList = mPersistAtomsStorage.getOutgoingSms(100L);
+        // Should be denied.
+        assertNull(outgoingShortCodeSmsList);
+    }
+
+    @Test
     public void addOutgoingShortCodeSms_emptyProto() throws Exception {
         createEmptyTestFile();
 
@@ -3910,6 +4397,30 @@
         expected.totalBatteryChargedTimeSec =
                 mSatelliteController1.totalBatteryChargedTimeSec
                         + mSatelliteController2.totalBatteryChargedTimeSec;
+        expected.countOfDemoModeSatelliteServiceEnablementsSuccess =
+                mSatelliteController1.countOfDemoModeSatelliteServiceEnablementsSuccess
+                        + mSatelliteController2.countOfDemoModeSatelliteServiceEnablementsSuccess;
+        expected.countOfDemoModeSatelliteServiceEnablementsFail =
+                mSatelliteController1.countOfDemoModeSatelliteServiceEnablementsFail
+                        + mSatelliteController2.countOfDemoModeSatelliteServiceEnablementsFail;
+        expected.countOfDemoModeOutgoingDatagramSuccess =
+                mSatelliteController1.countOfDemoModeOutgoingDatagramSuccess
+                        + mSatelliteController2.countOfDemoModeOutgoingDatagramSuccess;
+        expected.countOfDemoModeOutgoingDatagramFail =
+                mSatelliteController1.countOfDemoModeOutgoingDatagramFail
+                        + mSatelliteController2.countOfDemoModeOutgoingDatagramFail;
+        expected.countOfDemoModeIncomingDatagramSuccess =
+                mSatelliteController1.countOfDemoModeIncomingDatagramSuccess
+                        + mSatelliteController2.countOfDemoModeIncomingDatagramSuccess;
+        expected.countOfDemoModeIncomingDatagramFail =
+                mSatelliteController1.countOfDemoModeIncomingDatagramFail
+                        + mSatelliteController2.countOfDemoModeIncomingDatagramFail;
+        expected.countOfDatagramTypeKeepAliveSuccess =
+                mSatelliteController1.countOfDatagramTypeKeepAliveSuccess
+                        + mSatelliteController2.countOfDatagramTypeKeepAliveSuccess;
+        expected.countOfDatagramTypeKeepAliveFail =
+                mSatelliteController1.countOfDatagramTypeKeepAliveFail
+                        + mSatelliteController2.countOfDatagramTypeKeepAliveFail;
 
         // Service state and service switch should be added successfully
         verifyCurrentStateSavedToFileOnce();
@@ -4314,6 +4825,533 @@
     }
 
     @Test
+    public void addCarrierRoamingSatelliteSessionStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addCarrierRoamingSatelliteSessionStats(
+                mCarrierRoamingSatelliteSession1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+        CarrierRoamingSatelliteSession[] output =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteSessionStats(0L);
+        assertProtoArrayEquals(new CarrierRoamingSatelliteSession[] {
+                mCarrierRoamingSatelliteSession1}, output);
+    }
+
+    @Test
+    public void addCarrierRoamingSatelliteSessionStats_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addCarrierRoamingSatelliteSessionStats(
+                mCarrierRoamingSatelliteSession1);
+        mPersistAtomsStorage.addCarrierRoamingSatelliteSessionStats(
+                mCarrierRoamingSatelliteSession2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+        CarrierRoamingSatelliteSession[] output =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteSessionStats(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new CarrierRoamingSatelliteSession[] {mCarrierRoamingSatelliteSession2}, output);
+    }
+
+    @Test
+    public void addCarrierRoamingSatelliteSessionStats_tooManyEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        // Store atoms up to maximum number + 1
+        int maxCount = 1 + 1;
+        for (int i = 0; i < maxCount; i++) {
+            mPersistAtomsStorage.addCarrierRoamingSatelliteSessionStats(
+                    copyOf(mCarrierRoamingSatelliteSession1));
+            mPersistAtomsStorage.incTimeMillis(100L);
+        }
+
+        // Store 1 different atom
+        mPersistAtomsStorage.addCarrierRoamingSatelliteSessionStats(
+                mCarrierRoamingSatelliteSession2);
+
+        verifyCurrentStateSavedToFileOnce();
+
+        CarrierRoamingSatelliteSession[] result =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteSessionStats(0L);
+
+        // First atom has count 0, the other has 1
+        assertHasStatsAndCount(result, mCarrierRoamingSatelliteSession1, 0);
+        assertHasStatsAndCount(result, mCarrierRoamingSatelliteSession2, 1);
+    }
+
+    @Test
+    public void getCarrierRoamingSatelliteSessionStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        CarrierRoamingSatelliteSession[] output =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteSessionStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+    @Test
+    public void getCarrierRoamingSatelliteSessionStats_withSavedAtoms() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        CarrierRoamingSatelliteSession[] carrierRoamingSatelliteSessionStatsList1 =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteSessionStats(50L);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        CarrierRoamingSatelliteSession[] carrierRoamingSatelliteSessionStatsList2 =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteSessionStats(50L);
+
+        // First set of results should be equal to file contents.
+        CarrierRoamingSatelliteSession[] expectedList = new CarrierRoamingSatelliteSession[] {
+                mCarrierRoamingSatelliteSession1, mCarrierRoamingSatelliteSession2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList, carrierRoamingSatelliteSessionStatsList1);
+        // Second set of results should be empty.
+        assertProtoArrayEquals(new CarrierRoamingSatelliteSession[0],
+                carrierRoamingSatelliteSessionStatsList2);
+        // Corresponding pull timestamp should be updated and saved.
+        assertEquals(START_TIME_MILLIS + 200L, mPersistAtomsStorage
+                .getAtomsProto().carrierRoamingSatelliteSessionPullTimestampMillis);
+        InOrder inOrder = inOrder(mTestFileOutputStream);
+        assertEquals(START_TIME_MILLIS + 100L,
+                getAtomsWritten(inOrder).carrierRoamingSatelliteSessionPullTimestampMillis);
+        assertEquals(START_TIME_MILLIS + 200L,
+                getAtomsWritten(inOrder).carrierRoamingSatelliteSessionPullTimestampMillis);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void addCarrierRoamingSatelliteControllerStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addCarrierRoamingSatelliteControllerStats(
+                mCarrierRoamingSatelliteControllerStats1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+        CarrierRoamingSatelliteControllerStats[] output =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteControllerStats(0L);
+        assertProtoArrayEquals(new CarrierRoamingSatelliteControllerStats[] {
+                mCarrierRoamingSatelliteControllerStats1}, output);
+    }
+
+    @Test
+    public void addCarrierRoamingSatelliteControllerStats_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addCarrierRoamingSatelliteControllerStats(
+                mCarrierRoamingSatelliteControllerStats1);
+        mPersistAtomsStorage.addCarrierRoamingSatelliteControllerStats(
+                mCarrierRoamingSatelliteControllerStats2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        CarrierRoamingSatelliteControllerStats expected =
+                new CarrierRoamingSatelliteControllerStats();
+        expected.configDataSource = mCarrierRoamingSatelliteControllerStats2.configDataSource;
+        expected.countOfEntitlementStatusQueryRequest =
+                mCarrierRoamingSatelliteControllerStats1.countOfEntitlementStatusQueryRequest
+                        + mCarrierRoamingSatelliteControllerStats2
+                        .countOfEntitlementStatusQueryRequest;
+        expected.countOfSatelliteConfigUpdateRequest =
+                mCarrierRoamingSatelliteControllerStats1.countOfSatelliteConfigUpdateRequest
+                        + mCarrierRoamingSatelliteControllerStats2
+                        .countOfSatelliteConfigUpdateRequest;
+        expected.countOfSatelliteNotificationDisplayed =
+                mCarrierRoamingSatelliteControllerStats1.countOfSatelliteNotificationDisplayed
+                + mCarrierRoamingSatelliteControllerStats2
+                        .countOfSatelliteNotificationDisplayed;
+        expected.satelliteSessionGapMinSec =
+                mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapMinSec;
+        expected.satelliteSessionGapAvgSec =
+                mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapAvgSec;
+        expected.satelliteSessionGapMaxSec =
+                mCarrierRoamingSatelliteControllerStats2.satelliteSessionGapMaxSec;
+
+        verifyCurrentStateSavedToFileOnce();
+        CarrierRoamingSatelliteControllerStats[] output =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteControllerStats(0L);
+        assertHasStats(output, expected);
+    }
+
+    @Test
+    public void getCarrierRoamingSatelliteControllerStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        CarrierRoamingSatelliteControllerStats[] output =
+                mPersistAtomsStorage.getCarrierRoamingSatelliteControllerStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+
+    @Test
+    public void addSatelliteEntitlementStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteEntitlementStats(mSatelliteEntitlement1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteEntitlement[] output =
+                mPersistAtomsStorage.getSatelliteEntitlementStats(0L);
+        assertProtoArrayEquals(new SatelliteEntitlement[] {mSatelliteEntitlement1}, output);
+    }
+
+    @Test
+    public void addSatelliteEntitlementStats_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteEntitlementStats(mSatelliteEntitlement1);
+        mPersistAtomsStorage.addSatelliteEntitlementStats(mSatelliteEntitlement2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteEntitlement[] output =
+                mPersistAtomsStorage.getSatelliteEntitlementStats(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new SatelliteEntitlement[] {
+                        mSatelliteEntitlement1, mSatelliteEntitlement2}, output);
+    }
+
+    @Test
+    public void addSatelliteEntitlementStats_updateExistingEntries() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteEntitlementStats(copyOf(mSatelliteEntitlement1));
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Count should be increased by 1.
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteEntitlement newSatelliteEntitlement1 = copyOf(mSatelliteEntitlement1);
+        newSatelliteEntitlement1.count = 2;
+        SatelliteEntitlement[] expectedList = new SatelliteEntitlement[] {newSatelliteEntitlement1,
+                mSatelliteEntitlement2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList,
+                mPersistAtomsStorage.getSatelliteEntitlementStats(0L));
+    }
+
+    @Test
+    public void addSatelliteEntitlementStats_tooManyEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        // Store atoms up to maximum number + 1
+        int maxCount = 15 + 1;
+        for (int i = 0; i < maxCount; i++) {
+            mPersistAtomsStorage.addSatelliteEntitlementStats(mSatelliteEntitlement1);
+            mPersistAtomsStorage.incTimeMillis(100L);
+        }
+
+        // Store 1 different atom
+        mPersistAtomsStorage.addSatelliteEntitlementStats(mSatelliteEntitlement2);
+
+        verifyCurrentStateSavedToFileOnce();
+
+        SatelliteEntitlement[] result =
+                mPersistAtomsStorage.getSatelliteEntitlementStats(0L);
+
+        // First atom has count 14, the other has 1
+        assertHasStatsAndCount(result, mSatelliteEntitlement1, 16);
+        assertHasStatsAndCount(result, mSatelliteEntitlement2, 1);
+    }
+
+    @Test
+    public void getSatelliteEntitlementStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        SatelliteEntitlement[] output =
+                mPersistAtomsStorage.getSatelliteEntitlementStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+    @Test
+    public void getSatelliteEntitlementStats_withSavedAtoms() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        SatelliteEntitlement[] satelliteEntitlementStatsList1 =
+                mPersistAtomsStorage.getSatelliteEntitlementStats(50L);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        SatelliteEntitlement[] satelliteEntitlementStatsList2 =
+                mPersistAtomsStorage.getSatelliteEntitlementStats(50L);
+
+        // First set of results should be equal to file contents.
+        SatelliteEntitlement[] expectedList = new SatelliteEntitlement[] {
+                mSatelliteEntitlement1, mSatelliteEntitlement2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList, satelliteEntitlementStatsList1);
+        // Second set of results should be empty.
+        assertProtoArrayEquals(new SatelliteEntitlement[0], satelliteEntitlementStatsList2);
+        // Corresponding pull timestamp should be updated and saved.
+        assertEquals(START_TIME_MILLIS + 200L, mPersistAtomsStorage
+                .getAtomsProto().satelliteEntitlementPullTimestampMillis);
+        InOrder inOrder = inOrder(mTestFileOutputStream);
+        assertEquals(START_TIME_MILLIS + 100L,
+                getAtomsWritten(inOrder).satelliteEntitlementPullTimestampMillis);
+        assertEquals(START_TIME_MILLIS + 200L,
+                getAtomsWritten(inOrder).satelliteEntitlementPullTimestampMillis);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void addSatelliteConfigUpdaterStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteConfigUpdaterStats(mSatelliteConfigUpdater1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteConfigUpdater[] output =
+                mPersistAtomsStorage.getSatelliteConfigUpdaterStats(0L);
+        assertProtoArrayEquals(new SatelliteConfigUpdater[] {mSatelliteConfigUpdater1}, output);
+    }
+
+    @Test
+    public void addSatelliteConfigUpdaterStats_withExistingEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteConfigUpdaterStats(mSatelliteConfigUpdater1);
+        mPersistAtomsStorage.addSatelliteConfigUpdaterStats(mSatelliteConfigUpdater2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteConfigUpdater[] output =
+                mPersistAtomsStorage.getSatelliteConfigUpdaterStats(0L);
+        assertProtoArrayEqualsIgnoringOrder(new SatelliteConfigUpdater[] {
+                mSatelliteConfigUpdater1, mSatelliteConfigUpdater2}, output);
+    }
+
+    @Test
+    public void addSatelliteConfigUpdaterStats_updateExistingEntries() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteConfigUpdaterStats(copyOf(mSatelliteConfigUpdater1));
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Count should be increased by 1.
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteConfigUpdater newSatelliteConfigUpdater1 = copyOf(mSatelliteConfigUpdater1);
+        newSatelliteConfigUpdater1.count = 2;
+        SatelliteConfigUpdater[] expectedList = new SatelliteConfigUpdater[] {
+                newSatelliteConfigUpdater1, mSatelliteConfigUpdater2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList,
+                mPersistAtomsStorage.getSatelliteConfigUpdaterStats(0L));
+    }
+
+    @Test
+    public void addSatelliteConfigUpdaterStats_tooManyEntries() throws Exception {
+        createEmptyTestFile();
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        // Store atoms up to maximum number + 1
+        int maxCount = 15 + 1;
+        for (int i = 0; i < maxCount; i++) {
+            mPersistAtomsStorage.addSatelliteConfigUpdaterStats(mSatelliteConfigUpdater1);
+            mPersistAtomsStorage.incTimeMillis(100L);
+        }
+
+        // Store 1 different atom
+        mPersistAtomsStorage.addSatelliteConfigUpdaterStats(mSatelliteConfigUpdater2);
+
+        verifyCurrentStateSavedToFileOnce();
+
+        SatelliteConfigUpdater[] result =
+                mPersistAtomsStorage.getSatelliteConfigUpdaterStats(0L);
+
+        // First atom has count 14, the other has 1
+        assertHasStatsAndCount(result, mSatelliteConfigUpdater1, 16);
+        assertHasStatsAndCount(result, mSatelliteConfigUpdater2, 1);
+    }
+
+    @Test
+    public void getSatelliteConfigUpdaterStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        SatelliteConfigUpdater[] output =
+                mPersistAtomsStorage.getSatelliteConfigUpdaterStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+    @Test
+    public void getSatelliteConfigUpdaterStats_withSavedAtoms() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        SatelliteConfigUpdater[] satelliteConfigUpdaterStatsList1 =
+                mPersistAtomsStorage.getSatelliteConfigUpdaterStats(50L);
+        mPersistAtomsStorage.incTimeMillis(100L);
+        SatelliteConfigUpdater[] satelliteConfigUpdaterStatsList2 =
+                mPersistAtomsStorage.getSatelliteConfigUpdaterStats(50L);
+
+        // First set of results should be equal to file contents.
+        SatelliteConfigUpdater[] expectedList = new SatelliteConfigUpdater[] {
+                mSatelliteConfigUpdater1, mSatelliteConfigUpdater2};
+        assertProtoArrayEqualsIgnoringOrder(expectedList, satelliteConfigUpdaterStatsList1);
+        // Second set of results should be empty.
+        assertProtoArrayEquals(new SatelliteConfigUpdater[0], satelliteConfigUpdaterStatsList2);
+        // Corresponding pull timestamp should be updated and saved.
+        assertEquals(START_TIME_MILLIS + 200L, mPersistAtomsStorage
+                .getAtomsProto().satelliteConfigUpdaterPullTimestampMillis);
+        InOrder inOrder = inOrder(mTestFileOutputStream);
+        assertEquals(START_TIME_MILLIS + 100L,
+                getAtomsWritten(inOrder).satelliteConfigUpdaterPullTimestampMillis);
+        assertEquals(START_TIME_MILLIS + 200L,
+                getAtomsWritten(inOrder).satelliteConfigUpdaterPullTimestampMillis);
+        inOrder.verifyNoMoreInteractions();
+    }
+
+    @Test
+    public void addSatelliteAccessControllerStats_emptyProto() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.addSatelliteAccessControllerStats(
+                mSatelliteAccessController1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // Service state and service switch should be added successfully
+        verifyCurrentStateSavedToFileOnce();
+        SatelliteAccessController[] output =
+                mPersistAtomsStorage.getSatelliteAccessControllerStats(0L);
+        assertProtoArrayEquals(
+                new SatelliteAccessController[] {mSatelliteAccessController1}, output);
+    }
+
+    @Test
+    public void addSatelliteAccessControllerStats_tooManyEntries() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        // Store atoms up to maximum number + 1
+        int maxCount = 15 + 1;
+        for (int i = 0; i < maxCount; i++) {
+            mPersistAtomsStorage
+                    .addSatelliteAccessControllerStats(//mSatelliteAccessController1);
+                            copyOf(mSatelliteAccessController1));
+            mPersistAtomsStorage.incTimeMillis(100L);
+        }
+
+        // Store 1 different atom
+        mPersistAtomsStorage
+                .addSatelliteAccessControllerStats(mSatelliteAccessController2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        verifyCurrentStateSavedToFileOnce();
+
+        SatelliteAccessController[] result =
+                mPersistAtomsStorage.getSatelliteAccessControllerStats(0L);
+        // First atom should have count 14, the other should have 1
+        assertHasStatsAndCount(result, mSatelliteAccessController1, 14);
+        assertHasStatsAndCount(result, mSatelliteAccessController2, 1);
+    }
+
+    @Test
+    public void getSatelliteAccessControllerStats_tooFrequent() throws Exception {
+        createTestFile(START_TIME_MILLIS);
+
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+        mPersistAtomsStorage.incTimeMillis(50L); // pull interval less than minimum
+        SatelliteAccessController[] output =
+                mPersistAtomsStorage.getSatelliteAccessControllerStats(100L);
+
+        // Should be denied
+        assertNull(output);
+    }
+
+    @Test
+    @SmallTest
+    public void addDataNetworkValidation_newEntry() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        mPersistAtomsStorage.addDataNetworkValidation(mDataNetworkValidationLte1);
+        mPersistAtomsStorage.addDataNetworkValidation(mDataNetworkValidationIwlan1);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // There should be 2 DataNetworkValidation
+        verifyCurrentStateSavedToFileOnce();
+        DataNetworkValidation[] output = mPersistAtomsStorage.getDataNetworkValidation(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new DataNetworkValidation[] {
+                        mDataNetworkValidationLte1, mDataNetworkValidationIwlan1},
+                output);
+    }
+
+    @Test
+    public void addDataNetworkValidation_existingEntry() throws Exception {
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        int expectedNetworkValidationCount =
+                mDataNetworkValidationLte1.networkValidationCount
+                        + mDataNetworkValidationLte2.networkValidationCount;
+
+        mPersistAtomsStorage.addDataNetworkValidation(mDataNetworkValidationLte1);
+        mPersistAtomsStorage.addDataNetworkValidation(mDataNetworkValidationLte2);
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        DataNetworkValidation expected = copyOf(mDataNetworkValidationLte1);
+        expected.networkValidationCount = expectedNetworkValidationCount;
+
+        // There should be 1 DataNetworkValidation
+        verifyCurrentStateSavedToFileOnce();
+        DataNetworkValidation[] output =
+                mPersistAtomsStorage.getDataNetworkValidation(0L);
+        assertProtoArrayEqualsIgnoringOrder(
+                new DataNetworkValidation[] {expected},
+                output);
+    }
+
+    @Test
+    public void addDataNetworkValidation_tooManyEntries() throws Exception {
+
+        // Set up
+        doReturn(false).when(mPackageManager).hasSystemFeature(anyString());
+
+        createEmptyTestFile();
+        mPersistAtomsStorage = new TestablePersistAtomsStorage(mContext);
+
+        // Currently, the maximum number that can be stored for DataNetworkValidation in Atom
+        // storage is 15. Try saving excess.
+        int maxNumDataNetworkValidation = 20;
+        mPersistAtomsStorage.addDataNetworkValidation(mDataNetworkValidationLte1);
+        for (int i = 0; i < maxNumDataNetworkValidation; i++) {
+            DataNetworkValidation copied = copyOf(mDataNetworkValidationLte1);
+            copied.apnTypeBitmask = mDataNetworkValidationLte1.apnTypeBitmask + i;
+            mPersistAtomsStorage.addDataNetworkValidation(copied);
+        }
+        mPersistAtomsStorage.incTimeMillis(100L);
+
+        // There should be less than or equal to maxNumDataNetworkValidation
+        verifyCurrentStateSavedToFileOnce();
+        DataNetworkValidation[] output =
+                mPersistAtomsStorage.getDataNetworkValidation(0L);
+        assertTrue(output.length <= maxNumDataNetworkValidation);
+    }
+
+    @Test
     @SmallTest
     public void clearAtoms() throws Exception {
         createTestFile(START_TIME_MILLIS);
@@ -4398,6 +5436,17 @@
         atoms.satelliteProvisionPullTimestampMillis = lastPullTimeMillis;
         atoms.satelliteSosMessageRecommender = mSatelliteSosMessageRecommenders;
         atoms.satelliteSosMessageRecommenderPullTimestampMillis = lastPullTimeMillis;
+        atoms.dataNetworkValidation = mDataNetworkValidations;
+        atoms.dataNetworkValidationPullTimestampMillis = lastPullTimeMillis;
+        atoms.carrierRoamingSatelliteSession = mCarrierRoamingSatelliteSessions;
+        atoms.carrierRoamingSatelliteSessionPullTimestampMillis = lastPullTimeMillis;
+        atoms.carrierRoamingSatelliteControllerStats = mCarrierRoamingSatelliteControllerStats;
+        atoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis = lastPullTimeMillis;
+        atoms.satelliteEntitlement = mSatelliteEntitlements;
+        atoms.satelliteEntitlementPullTimestampMillis = lastPullTimeMillis;
+        atoms.satelliteConfigUpdater = mSatelliteConfigUpdaters;
+        atoms.satelliteConfigUpdaterPullTimestampMillis = lastPullTimeMillis;
+        atoms.satelliteAccessControllerPullTimestampMillis = lastPullTimeMillis;
         FileOutputStream stream = new FileOutputStream(mTestFile);
         stream.write(PersistAtoms.toByteArray(atoms));
         stream.close();
@@ -4516,6 +5565,16 @@
         return SipTransportSession.parseFrom(MessageNano.toByteArray(source));
     }
 
+    private static IncomingSms copyOf(IncomingSms source)
+            throws Exception {
+        return IncomingSms.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static OutgoingSms copyOf(OutgoingSms source)
+            throws Exception {
+        return OutgoingSms.parseFrom(MessageNano.toByteArray(source));
+    }
+
     private static OutgoingShortCodeSms copyOf(OutgoingShortCodeSms source)
             throws Exception {
         return OutgoingShortCodeSms.parseFrom(MessageNano.toByteArray(source));
@@ -4551,6 +5610,34 @@
         return SatelliteSosMessageRecommender.parseFrom(MessageNano.toByteArray(source));
     }
 
+    private static DataNetworkValidation copyOf(DataNetworkValidation source)
+            throws Exception {
+        return DataNetworkValidation.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static CarrierRoamingSatelliteSession copyOf(CarrierRoamingSatelliteSession source)
+            throws Exception {
+        return CarrierRoamingSatelliteSession.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static CarrierRoamingSatelliteControllerStats copyOf(
+            CarrierRoamingSatelliteControllerStats source) throws Exception {
+        return CarrierRoamingSatelliteControllerStats.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static SatelliteEntitlement copyOf(SatelliteEntitlement source) throws Exception {
+        return SatelliteEntitlement.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static SatelliteConfigUpdater copyOf(SatelliteConfigUpdater source) throws Exception {
+        return SatelliteConfigUpdater.parseFrom(MessageNano.toByteArray(source));
+    }
+
+    private static SatelliteAccessController copyOf(SatelliteAccessController source)
+            throws Exception {
+        return SatelliteAccessController.parseFrom(MessageNano.toByteArray(source));
+    }
+
     private void assertAllPullTimestampEquals(long timestamp) {
         assertEquals(
                 timestamp,
@@ -4726,6 +5813,18 @@
                 expectedStats.totalBatteryConsumptionPercent);
         assertEquals(tested[0].totalBatteryChargedTimeSec,
                 expectedStats.totalBatteryChargedTimeSec);
+        assertEquals(tested[0].countOfDemoModeSatelliteServiceEnablementsSuccess,
+                expectedStats.countOfDemoModeSatelliteServiceEnablementsSuccess);
+        assertEquals(tested[0].countOfDemoModeSatelliteServiceEnablementsFail,
+                expectedStats.countOfDemoModeSatelliteServiceEnablementsFail);
+        assertEquals(tested[0].countOfDemoModeOutgoingDatagramSuccess,
+                expectedStats.countOfDemoModeOutgoingDatagramSuccess);
+        assertEquals(tested[0].countOfDemoModeOutgoingDatagramFail,
+                expectedStats.countOfDemoModeOutgoingDatagramFail);
+        assertEquals(tested[0].countOfDemoModeIncomingDatagramSuccess,
+                expectedStats.countOfDemoModeIncomingDatagramSuccess);
+        assertEquals(tested[0].countOfDemoModeIncomingDatagramFail,
+                expectedStats.countOfDemoModeIncomingDatagramFail);
     }
 
     private static void assertHasStatsAndCount(
@@ -4736,7 +5835,23 @@
         for (SatelliteSession stats : tested) {
             if (stats.satelliteServiceInitializationResult
                     == expectedStats.satelliteServiceInitializationResult
-                    && stats.satelliteTechnology == expectedStats.satelliteTechnology) {
+                    && stats.satelliteTechnology == expectedStats.satelliteTechnology
+                    && stats.satelliteServiceTerminationResult
+                        == expectedStats.satelliteServiceTerminationResult
+                    && stats.initializationProcessingTimeMillis
+                        == expectedStats.initializationProcessingTimeMillis
+                    && stats.terminationProcessingTimeMillis
+                        == expectedStats.terminationProcessingTimeMillis
+                    && stats.sessionDurationSeconds == expectedStats.sessionDurationSeconds
+                    && stats.countOfOutgoingDatagramSuccess
+                        == expectedStats.countOfOutgoingDatagramSuccess
+                    && stats.countOfOutgoingDatagramFailed
+                        == expectedStats.countOfOutgoingDatagramFailed
+                    && stats.countOfIncomingDatagramSuccess
+                        == expectedStats.countOfIncomingDatagramSuccess
+                    && stats.countOfIncomingDatagramFailed
+                        == expectedStats.countOfIncomingDatagramFailed
+                    && stats.isDemoMode == expectedStats.isDemoMode) {
                 actualCount = stats.count;
             }
         }
@@ -4752,7 +5867,8 @@
             if (stats.resultCode == expectedStats.resultCode
                     && stats.datagramSizeBytes == expectedStats.datagramSizeBytes
                     && stats.datagramTransferTimeMillis
-                        == expectedStats.datagramTransferTimeMillis) {
+                        == expectedStats.datagramTransferTimeMillis
+                    && stats.isDemoMode == expectedStats.isDemoMode) {
                 actualCount++;
             }
         }
@@ -4769,7 +5885,8 @@
                     && stats.resultCode == expectedStats.resultCode
                     && stats.datagramSizeBytes == expectedStats.datagramSizeBytes
                     && stats.datagramTransferTimeMillis
-                        == expectedStats.datagramTransferTimeMillis) {
+                        == expectedStats.datagramTransferTimeMillis
+                    && stats.isDemoMode == expectedStats.isDemoMode) {
                 actualCount++;
             }
         }
@@ -4813,6 +5930,28 @@
         assertEquals(expectedCount, actualCount);
     }
 
+    private static void assertHasStatsAndCount(
+            SatelliteAccessController[] tested,
+            @Nullable SatelliteAccessController expectedStats, int expectedCount) {
+        assertNotNull(tested);
+        int actualCount = 0;
+        for (SatelliteAccessController stats : tested) {
+            if (stats.accessControlType
+                    == expectedStats.accessControlType
+                    && stats.locationQueryTimeMillis == expectedStats.locationQueryTimeMillis
+                    && stats.onDeviceLookupTimeMillis == expectedStats.onDeviceLookupTimeMillis
+                    && stats.totalCheckingTimeMillis == expectedStats.totalCheckingTimeMillis
+                    && stats.isAllowed == expectedStats.isAllowed
+                    && stats.isEmergency == expectedStats.isEmergency
+                    && stats.resultCode == expectedStats.resultCode
+                    && Arrays.equals(stats.countryCodes, expectedStats.countryCodes)
+                    && stats.configDataSource == expectedStats.configDataSource) {
+                actualCount++;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
     private static void assertHasStatsAndCountDuration(
             RcsAcsProvisioningStats[] statses,
             @Nullable RcsAcsProvisioningStats expectedStats, int count, long duration) {
@@ -4949,6 +6088,65 @@
     }
 
     private static void assertHasStatsAndCount(
+            IncomingSms[] incomingSmsList,
+            @Nullable IncomingSms expectedIncomingSms, int expectedCount) {
+        assertNotNull(incomingSmsList);
+        int actualCount = -1;
+        for (IncomingSms incomingSms : incomingSmsList) {
+            if (incomingSms.smsFormat == expectedIncomingSms.smsFormat
+                    && incomingSms.smsTech == expectedIncomingSms.smsTech
+                    && incomingSms.rat == expectedIncomingSms.rat
+                    && incomingSms.smsType == expectedIncomingSms.smsType
+                    && incomingSms.totalParts == expectedIncomingSms.totalParts
+                    && incomingSms.receivedParts == expectedIncomingSms.receivedParts
+                    && incomingSms.blocked == expectedIncomingSms.blocked
+                    && incomingSms.error == expectedIncomingSms.error
+                    && incomingSms.isRoaming == expectedIncomingSms.isRoaming
+                    && incomingSms.simSlotIndex == expectedIncomingSms.simSlotIndex
+                    && incomingSms.isMultiSim == expectedIncomingSms.isMultiSim
+                    && incomingSms.isEsim == expectedIncomingSms.isEsim
+                    && incomingSms.carrierId == expectedIncomingSms.carrierId
+                    && incomingSms.messageId == expectedIncomingSms.messageId
+                    && incomingSms.isManagedProfile == expectedIncomingSms.isManagedProfile
+                    && incomingSms.isNtn == expectedIncomingSms.isNtn
+                    && incomingSms.isEmergency == expectedIncomingSms.isEmergency) {
+                actualCount = incomingSms.count;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
+    private static void assertHasStatsAndCount(
+            OutgoingSms[] outgoingSmsList,
+            @Nullable OutgoingSms expectedOutgoingSms, int expectedCount) {
+        assertNotNull(outgoingSmsList);
+        int actualCount = -1;
+        for (OutgoingSms outgoingSms : outgoingSmsList) {
+            if (outgoingSms.smsFormat == expectedOutgoingSms.smsFormat
+                    && outgoingSms.smsTech == expectedOutgoingSms.smsTech
+                    && outgoingSms.rat == expectedOutgoingSms.rat
+                    && outgoingSms.sendResult == expectedOutgoingSms.sendResult
+                    && outgoingSms.errorCode == expectedOutgoingSms.errorCode
+                    && outgoingSms.isRoaming == expectedOutgoingSms.isRoaming
+                    && outgoingSms.isFromDefaultApp == expectedOutgoingSms.isFromDefaultApp
+                    && outgoingSms.simSlotIndex == expectedOutgoingSms.simSlotIndex
+                    && outgoingSms.isMultiSim == expectedOutgoingSms.isMultiSim
+                    && outgoingSms.carrierId == expectedOutgoingSms.carrierId
+                    && outgoingSms.messageId == expectedOutgoingSms.messageId
+                    && outgoingSms.retryId == expectedOutgoingSms.retryId
+                    && outgoingSms.intervalMillis == expectedOutgoingSms.intervalMillis
+                    && outgoingSms.sendErrorCode == expectedOutgoingSms.sendErrorCode
+                    && outgoingSms.networkErrorCode == expectedOutgoingSms.networkErrorCode
+                    && outgoingSms.isManagedProfile == expectedOutgoingSms.isManagedProfile
+                    && outgoingSms.isEmergency == expectedOutgoingSms.isEmergency
+                    && outgoingSms.isNtn == expectedOutgoingSms.isNtn) {
+                actualCount = outgoingSms.count;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
+    private static void assertHasStatsAndCount(
             OutgoingShortCodeSms[] outgoingShortCodeSmsList,
             @Nullable OutgoingShortCodeSms expectedOutgoingShortCodeSms, int expectedCount) {
         assertNotNull(outgoingShortCodeSmsList);
@@ -4961,4 +6159,82 @@
         }
         assertEquals(expectedCount, actualCount);
     }
+
+    private static void assertHasStatsAndCount(CarrierRoamingSatelliteSession[] tested,
+            @Nullable CarrierRoamingSatelliteSession expectedStats, int expectedCount) {
+        assertNotNull(tested);
+        int actualCount = 0;
+        for (CarrierRoamingSatelliteSession stats : tested) {
+            if (stats.carrierId == expectedStats.carrierId
+                    && stats.isNtnRoamingInHomeCountry == expectedStats.isNtnRoamingInHomeCountry
+                    && stats.totalSatelliteModeTimeSec == expectedStats.totalSatelliteModeTimeSec
+                    && stats.numberOfSatelliteConnections
+                    == expectedStats.numberOfSatelliteConnections
+                    && stats.avgDurationOfSatelliteConnectionSec
+                    == expectedStats.avgDurationOfSatelliteConnectionSec
+                    && stats.satelliteConnectionGapMinSec
+                    == expectedStats.satelliteConnectionGapMinSec
+                    && stats.satelliteConnectionGapAvgSec
+                    == expectedStats.satelliteConnectionGapAvgSec
+                    && stats.satelliteConnectionGapMaxSec
+                    == expectedStats.satelliteConnectionGapMaxSec
+                    && stats.rsrpAvg == expectedStats.rsrpAvg
+                    && stats.rsrpMedian == expectedStats.rsrpMedian
+                    && stats.rssnrAvg == expectedStats.rssnrAvg
+                    && stats.rssnrMedian == expectedStats.rssnrMedian
+                    && stats.countOfIncomingSms == expectedStats.countOfIncomingSms
+                    && stats.countOfOutgoingSms == expectedStats.countOfOutgoingSms
+                    && stats.countOfIncomingMms == expectedStats.countOfIncomingMms
+                    && stats.countOfOutgoingMms == expectedStats.countOfOutgoingMms) {
+                actualCount++;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
+    private static void assertHasStats(CarrierRoamingSatelliteControllerStats[] tested,
+            @Nullable CarrierRoamingSatelliteControllerStats expectedStats) {
+        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);
+    }
+
+    private static void assertHasStatsAndCount(
+            SatelliteEntitlement[] tested,
+            @Nullable SatelliteEntitlement expectedStats, int expectedCount) {
+        assertNotNull(tested);
+        int actualCount = 0;
+        for (SatelliteEntitlement stats : tested) {
+            if (stats.carrierId == expectedStats.carrierId
+                    && stats.result == expectedStats.result
+                    && stats.entitlementStatus == expectedStats.entitlementStatus
+                    && stats.isRetry == expectedStats.isRetry) {
+                actualCount = stats.count;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
+
+    private static void assertHasStatsAndCount(
+            SatelliteConfigUpdater[] tested,
+            @Nullable SatelliteConfigUpdater expectedStats, int expectedCount) {
+        assertNotNull(tested);
+        int actualCount = 0;
+        for (SatelliteConfigUpdater stats : tested) {
+            if (stats.configVersion == expectedStats.configVersion
+                    && stats.oemConfigResult == expectedStats.oemConfigResult
+                    && stats.carrierConfigResult == expectedStats.carrierConfigResult) {
+                actualCount = stats.count;
+            }
+        }
+        assertEquals(expectedCount, actualCount);
+    }
 }
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 959b643..cda96ef 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
@@ -16,6 +16,12 @@
 
 package com.android.internal.telephony.metrics;
 
+import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_GOOD;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
+
+import static com.android.internal.telephony.satellite.SatelliteConstants.CONFIG_DATA_SOURCE_DEVICE_CONFIG;
+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.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -24,7 +30,12 @@
 import android.telephony.TelephonyProtoEnums;
 
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteControllerStats;
+import com.android.internal.telephony.nano.PersistAtomsProto.CarrierRoamingSatelliteSession;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteAccessController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteConfigUpdater;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteController;
+import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteEntitlement;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteIncomingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteOutgoingDatagram;
 import com.android.internal.telephony.nano.PersistAtomsProto.SatelliteProvision;
@@ -36,6 +47,8 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import java.util.concurrent.TimeUnit;
+
 public class SatelliteStatsTest extends TelephonyTest {
     private static final String TAG = SatelliteStatsTest.class.getSimpleName();
 
@@ -81,6 +94,14 @@
                         .setTotalServiceUptimeSec(60 * 60 * 24 * 7)
                         .setTotalBatteryConsumptionPercent(7)
                         .setTotalBatteryChargedTimeSec(60 * 60 * 3)
+                        .setCountOfDemoModeSatelliteServiceEnablementsSuccess(3)
+                        .setCountOfDemoModeSatelliteServiceEnablementsFail(1)
+                        .setCountOfDemoModeOutgoingDatagramSuccess(4)
+                        .setCountOfDemoModeOutgoingDatagramFail(2)
+                        .setCountOfDemoModeIncomingDatagramSuccess(3)
+                        .setCountOfDemoModeIncomingDatagramFail(2)
+                        .setCountOfDatagramTypeKeepAliveSuccess(1)
+                        .setCountOfDatagramTypeKeepAliveFail(2)
                         .build();
 
         mSatelliteStats.onSatelliteControllerMetrics(param);
@@ -123,6 +144,22 @@
                 stats.totalBatteryConsumptionPercent);
         assertEquals(param.getTotalBatteryChargedTimeSec(),
                 stats.totalBatteryChargedTimeSec);
+        assertEquals(param.getCountOfDemoModeSatelliteServiceEnablementsSuccess(),
+                stats.countOfDemoModeSatelliteServiceEnablementsSuccess);
+        assertEquals(param.getCountOfDemoModeSatelliteServiceEnablementsFail(),
+                stats.countOfDemoModeSatelliteServiceEnablementsFail);
+        assertEquals(param.getCountOfDemoModeOutgoingDatagramSuccess(),
+                stats.countOfDemoModeOutgoingDatagramSuccess);
+        assertEquals(param.getCountOfDemoModeOutgoingDatagramFail(),
+                stats.countOfDemoModeOutgoingDatagramFail);
+        assertEquals(param.getCountOfDemoModeIncomingDatagramSuccess(),
+                stats.countOfDemoModeIncomingDatagramSuccess);
+        assertEquals(param.getCountOfDemoModeIncomingDatagramFail(),
+                stats.countOfDemoModeIncomingDatagramFail);
+        assertEquals(param.getCountOfDatagramTypeKeepAliveSuccess(),
+                stats.countOfDatagramTypeKeepAliveSuccess);
+        assertEquals(param.getCountOfDatagramTypeKeepAliveFail(),
+                stats.countOfDatagramTypeKeepAliveFail);
 
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
@@ -134,6 +171,16 @@
                         .setSatelliteServiceInitializationResult(
                                 SatelliteProtoEnums.SATELLITE_ERROR_NONE)
                         .setSatelliteTechnology(SatelliteProtoEnums.NT_RADIO_TECHNOLOGY_PROPRIETARY)
+                        .setTerminationResult(SatelliteProtoEnums.SATELLITE_ERROR_NONE)
+                        .setInitializationProcessingTime(100)
+                        .setTerminationProcessingTime(200)
+                        .setSessionDuration(3)
+                        .setCountOfOutgoingDatagramSuccess(1)
+                        .setCountOfOutgoingDatagramFailed(0)
+                        .setCountOfIncomingDatagramSuccess(1)
+                        .setCountOfIncomingDatagramFailed(0)
+                        .setIsDemoMode(false)
+                        .setMaxNtnSignalStrengthLevel(NTN_SIGNAL_STRENGTH_GOOD)
                         .build();
 
         mSatelliteStats.onSatelliteSessionMetrics(param);
@@ -144,7 +191,20 @@
         SatelliteSession stats = captor.getValue();
         assertEquals(param.getSatelliteServiceInitializationResult(),
                 stats.satelliteServiceInitializationResult);
-        assertEquals(param.getSatelliteTechnology(), stats.satelliteTechnology);
+        assertEquals(param.getTerminationResult(), stats.satelliteServiceTerminationResult);
+        assertEquals(param.getInitializationProcessingTime(),
+                stats.initializationProcessingTimeMillis);
+        assertEquals(param.getTerminationProcessingTime(), stats.terminationProcessingTimeMillis);
+        assertEquals(param.getSessionDuration(), stats.sessionDurationSeconds);
+        assertEquals(param.getCountOfOutgoingDatagramSuccess(),
+                stats.countOfOutgoingDatagramSuccess);
+        assertEquals(param.getCountOfOutgoingDatagramFailed(), stats.countOfOutgoingDatagramFailed);
+        assertEquals(param.getCountOfIncomingDatagramSuccess(),
+                stats.countOfIncomingDatagramSuccess);
+        assertEquals(param.getCountOfIncomingDatagramFailed(), stats.countOfIncomingDatagramFailed);
+        assertEquals(param.getIsDemoMode(), stats.isDemoMode);
+        assertEquals(param.getMaxNtnSignalStrengthLevel(), stats.maxNtnSignalStrengthLevel);
+
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -155,6 +215,7 @@
                         .setResultCode(SatelliteProtoEnums.SATELLITE_ERROR_NONE)
                         .setDatagramSizeBytes(1 * 1024)
                         .setDatagramTransferTimeMillis(3 * 1000)
+                        .setIsDemoMode(true)
                         .build();
 
         mSatelliteStats.onSatelliteIncomingDatagramMetrics(param);
@@ -166,6 +227,7 @@
         assertEquals(param.getResultCode(), stats.resultCode);
         assertEquals(param.getDatagramSizeBytes(), stats.datagramSizeBytes);
         assertEquals(param.getDatagramTransferTimeMillis(), stats.datagramTransferTimeMillis);
+        assertEquals(param.getIsDemoMode(), stats.isDemoMode);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -177,6 +239,7 @@
                         .setResultCode(SatelliteProtoEnums.SATELLITE_ERROR_NONE)
                         .setDatagramSizeBytes(1 * 1024)
                         .setDatagramTransferTimeMillis(3 * 1000)
+                        .setIsDemoMode(true)
                         .build();
 
         mSatelliteStats.onSatelliteOutgoingDatagramMetrics(param);
@@ -189,6 +252,7 @@
         assertEquals(param.getResultCode(), stats.resultCode);
         assertEquals(param.getDatagramSizeBytes(), stats.datagramSizeBytes);
         assertEquals(param.getDatagramTransferTimeMillis(), stats.datagramTransferTimeMillis);
+        assertEquals(param.getIsDemoMode(), stats.isDemoMode);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
@@ -247,4 +311,170 @@
                 stats.isSatelliteAllowedInCurrentLocation);
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
+
+    @Test
+    public void onCarrierRoamingSatelliteSessionMetrics_withAtoms() throws Exception {
+        SatelliteStats.CarrierRoamingSatelliteSessionParams param =
+                new SatelliteStats.CarrierRoamingSatelliteSessionParams.Builder()
+                        .setCarrierId(100)
+                        .setIsNtnRoamingInHomeCountry(true)
+                        .setTotalSatelliteModeTimeSec(10 * 60)
+                        .setNumberOfSatelliteConnections(5)
+                        .setAvgDurationOfSatelliteConnectionSec(2 * 60)
+                        .setSatelliteConnectionGapMinSec(30)
+                        .setSatelliteConnectionGapAvgSec(300)
+                        .setSatelliteConnectionGapMaxSec(500)
+                        .setRsrpAvg(2)
+                        .setRsrpMedian(3)
+                        .setRssnrAvg(12)
+                        .setRssnrMedian(18)
+                        .setCountOfIncomingSms(6)
+                        .setCountOfOutgoingSms(11)
+                        .setCountOfIncomingMms(9)
+                        .setCountOfOutgoingMms(14)
+                        .build();
+
+        mSatelliteStats.onCarrierRoamingSatelliteSessionMetrics(param);
+
+        ArgumentCaptor<CarrierRoamingSatelliteSession> captor =
+                ArgumentCaptor.forClass(CarrierRoamingSatelliteSession.class);
+        verify(mPersistAtomsStorage).addCarrierRoamingSatelliteSessionStats(captor.capture());
+        CarrierRoamingSatelliteSession stats = captor.getValue();
+        assertEquals(param.getCarrierId(), stats.carrierId);
+        assertEquals(param.getIsNtnRoamingInHomeCountry(), stats.isNtnRoamingInHomeCountry);
+        assertEquals(param.getTotalSatelliteModeTimeSec(), stats.totalSatelliteModeTimeSec);
+        assertEquals(param.getNumberOfSatelliteConnections(), stats.numberOfSatelliteConnections);
+        assertEquals(param.getAvgDurationOfSatelliteConnectionSec(),
+                stats.avgDurationOfSatelliteConnectionSec);
+        assertEquals(param.getSatelliteConnectionGapMinSec(), stats.satelliteConnectionGapMinSec);
+        assertEquals(param.getSatelliteConnectionGapAvgSec(), stats.satelliteConnectionGapAvgSec);
+        assertEquals(param.getSatelliteConnectionGapMaxSec(), stats.satelliteConnectionGapMaxSec);
+        assertEquals(param.getRsrpAvg(), stats.rsrpAvg);
+        assertEquals(param.getRsrpMedian(), stats.rsrpMedian);
+        assertEquals(param.getRssnrAvg(), stats.rssnrAvg);
+        assertEquals(param.getRssnrMedian(), stats.rssnrMedian);
+        assertEquals(param.getCountOfIncomingSms(), stats.countOfIncomingSms);
+        assertEquals(param.getCountOfOutgoingSms(), stats.countOfOutgoingSms);
+        assertEquals(param.getCountOfIncomingMms(), stats.countOfIncomingMms);
+        assertEquals(param.getCountOfOutgoingMms(), stats.countOfOutgoingMms);
+
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onCarrierRoamingSatelliteControllerStatsMetrics_withAtoms() throws Exception {
+        SatelliteStats.CarrierRoamingSatelliteControllerStatsParams param =
+                new SatelliteStats.CarrierRoamingSatelliteControllerStatsParams.Builder()
+                        .setConfigDataSource(4)
+                        .setCountOfEntitlementStatusQueryRequest(6)
+                        .setCountOfSatelliteConfigUpdateRequest(2)
+                        .setCountOfSatelliteNotificationDisplayed(1)
+                        .setSatelliteSessionGapMinSec(15)
+                        .setSatelliteSessionGapAvgSec(30)
+                        .setSatelliteSessionGapMaxSec(45)
+                        .build();
+
+        mSatelliteStats.onCarrierRoamingSatelliteControllerStatsMetrics(param);
+
+        ArgumentCaptor<CarrierRoamingSatelliteControllerStats> captor =
+                ArgumentCaptor.forClass(CarrierRoamingSatelliteControllerStats.class);
+        verify(mPersistAtomsStorage).addCarrierRoamingSatelliteControllerStats(captor.capture());
+        CarrierRoamingSatelliteControllerStats stats = captor.getValue();
+        assertEquals(param.getConfigDataSource(), stats.configDataSource);
+        assertEquals(param.getCountOfEntitlementStatusQueryRequest(),
+                stats.countOfEntitlementStatusQueryRequest);
+        assertEquals(param.getCountOfSatelliteConfigUpdateRequest(),
+                stats.countOfSatelliteConfigUpdateRequest);
+        assertEquals(param.getCountOfSatelliteNotificationDisplayed(),
+                stats.countOfSatelliteNotificationDisplayed);
+        assertEquals(param.getSatelliteSessionGapMinSec(), stats.satelliteSessionGapMinSec);
+        assertEquals(param.getSatelliteSessionGapAvgSec(), stats.satelliteSessionGapAvgSec);
+        assertEquals(param.getSatelliteSessionGapMaxSec(), stats.satelliteSessionGapMaxSec);
+
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onSatelliteEntitlementMetrics_withAtoms() throws Exception {
+        SatelliteStats.SatelliteEntitlementParams param =
+                new SatelliteStats.SatelliteEntitlementParams.Builder()
+                        .setCarrierId(10)
+                        .setResult(500)
+                        .setEntitlementStatus(2)
+                        .setIsRetry(true)
+                        .setCount(5)
+                        .build();
+
+        mSatelliteStats.onSatelliteEntitlementMetrics(param);
+
+        ArgumentCaptor<SatelliteEntitlement> captor =
+                ArgumentCaptor.forClass(SatelliteEntitlement.class);
+        verify(mPersistAtomsStorage).addSatelliteEntitlementStats(captor.capture());
+        SatelliteEntitlement stats = captor.getValue();
+        assertEquals(param.getCarrierId(), stats.carrierId);
+        assertEquals(param.getResult(), stats.result);
+        assertEquals(param.getEntitlementStatus(), stats.entitlementStatus);
+        assertEquals(param.getIsRetry(), stats.isRetry);
+        assertEquals(param.getCount(), stats.count);
+
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+    @Test
+    public void onSatelliteConfigUpdaterMetrics_withAtoms() throws Exception {
+        SatelliteStats.SatelliteConfigUpdaterParams param =
+                new SatelliteStats.SatelliteConfigUpdaterParams.Builder()
+                        .setConfigVersion(8)
+                        .setOemConfigResult(9)
+                        .setCarrierConfigResult(7)
+                        .setCount(3)
+                        .build();
+
+        mSatelliteStats.onSatelliteConfigUpdaterMetrics(param);
+
+        ArgumentCaptor<SatelliteConfigUpdater> captor =
+                ArgumentCaptor.forClass(SatelliteConfigUpdater.class);
+        verify(mPersistAtomsStorage).addSatelliteConfigUpdaterStats(captor.capture());
+        SatelliteConfigUpdater stats = captor.getValue();
+        assertEquals(param.getConfigVersion(), stats.configVersion);
+        assertEquals(param.getOemConfigResult(), stats.oemConfigResult);
+        assertEquals(param.getCarrierConfigResult(), stats.carrierConfigResult);
+        assertEquals(param.getCount(), stats.count);
+
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
+
+
+    @Test
+    public void onSatelliteAccessControllerMetrics_withAtoms() {
+        SatelliteStats.SatelliteAccessControllerParams param =
+                new SatelliteStats.SatelliteAccessControllerParams.Builder()
+                        .setAccessControlType(ACCESS_CONTROL_TYPE_CACHED_COUNTRY_CODE)
+                        .setLocationQueryTime(TimeUnit.SECONDS.toMillis(1))
+                        .setOnDeviceLookupTime(TimeUnit.SECONDS.toMillis(2))
+                        .setTotalCheckingTime(TimeUnit.SECONDS.toMillis(3))
+                        .setIsAllowed(true)
+                        .setIsEmergency(false)
+                        .setResult(SATELLITE_RESULT_SUCCESS)
+                        .setCountryCodes(new String[]{"AB", "CD"})
+                        .setConfigDatasource(CONFIG_DATA_SOURCE_DEVICE_CONFIG)
+                        .build();
+
+        mSatelliteStats.onSatelliteAccessControllerMetrics(param);
+
+        ArgumentCaptor<SatelliteAccessController> captor = ArgumentCaptor.forClass(
+                SatelliteAccessController.class);
+        verify(mPersistAtomsStorage).addSatelliteAccessControllerStats(captor.capture());
+        SatelliteAccessController stats = captor.getValue();
+        assertEquals(param.getAccessControlType(), stats.accessControlType);
+        assertEquals(param.getLocationQueryTime(), stats.locationQueryTimeMillis);
+        assertEquals(param.getOnDeviceLookupTime(), stats.onDeviceLookupTimeMillis);
+        assertEquals(param.getTotalCheckingTime(), stats.totalCheckingTimeMillis);
+        assertEquals(param.getIsAllowed(), stats.isAllowed);
+        assertEquals(param.getIsEmergency(), stats.isEmergency);
+        assertEquals(param.getResultCode(), stats.resultCode);
+        assertEquals(param.getCountryCodes(), stats.countryCodes);
+        assertEquals(param.getConfigDataSource(), stats.configDataSource);
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+    }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
index 16dab44..27878d1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/ServiceStateStatsTest.java
@@ -30,15 +30,20 @@
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation.NetworkType;
 import android.telephony.NetworkRegistrationInfo;
@@ -58,6 +63,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
@@ -76,6 +82,9 @@
 
     private TestableServiceStateStats mServiceStateStats;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static class TestableServiceStateStats extends ServiceStateStats {
         private long mTimeMillis = START_TIME_MILLIS;
 
@@ -1324,6 +1333,34 @@
         verifyNoMoreInteractions(mPersistAtomsStorage);
     }
 
+    @Test
+    public void testIsNtn() {
+        // Using default service state for LTE
+        mServiceStateStats.onServiceStateChanged(mServiceState);
+        mServiceStateStats.incTimeMillis(100L);
+        mServiceStateStats.conclude();
+
+        ArgumentCaptor<CellularServiceState> captor =
+                ArgumentCaptor.forClass(CellularServiceState.class);
+        verify(mPersistAtomsStorage)
+                .addCellularServiceStateAndCellularDataServiceSwitch(captor.capture(), eq(null));
+        CellularServiceState state = captor.getValue();
+        assertFalse(state.isNtn);
+
+        reset(mPersistAtomsStorage);
+        reset(mServiceState);
+
+        when(mSatelliteController.isInSatelliteModeForCarrierRoaming(any())).thenReturn(true);
+        mServiceStateStats.onServiceStateChanged(mServiceState);
+        mServiceStateStats.incTimeMillis(100L);
+        mServiceStateStats.conclude();
+
+        verify(mPersistAtomsStorage)
+                .addCellularServiceStateAndCellularDataServiceSwitch(captor.capture(), eq(null));
+        state = captor.getValue();
+        assertTrue(state.isNtn);
+    }
+
     private void mockWwanPsRat(@NetworkType int rat) {
         mockWwanRat(
                 NetworkRegistrationInfo.DOMAIN_PS,
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 58cc516..04d140c 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/VoiceCallSessionStatsTest.java
@@ -34,20 +34,26 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
 import android.os.Looper;
+import android.os.PersistableBundle;
 import android.telephony.AccessNetworkConstants;
 import android.telephony.Annotation.NetworkType;
+import android.telephony.CarrierConfigManager;
 import android.telephony.DisconnectCause;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PreciseDataConnectionState;
@@ -222,7 +228,8 @@
 
         mVoiceCallSessionStats0 = new TestableVoiceCallSessionStats(0, mPhone, mFeatureFlags);
         mVoiceCallSessionStats0.onServiceStateChanged(mServiceState);
-        mVoiceCallSessionStats1 = new TestableVoiceCallSessionStats(1, mSecondPhone, mFeatureFlags);
+        mVoiceCallSessionStats1 = new TestableVoiceCallSessionStats(
+                1, mSecondPhone, mFeatureFlags);
         mVoiceCallSessionStats1.onServiceStateChanged(mSecondServiceState);
 
         doReturn(true).when(mFeatureFlags).vonrEnabledMetric();
@@ -2753,6 +2760,102 @@
         assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
     }
 
+    @Test
+    public void testIsNtn() {
+        when(mSatelliteController.isInSatelliteModeForCarrierRoaming(any())).thenReturn(true);
+
+        mVoiceCallSessionStats0.onImsDial(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        mVoiceCallSessionStats0.onImsCallTerminated(mImsConnection0,
+                new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        VoiceCallSession session = callCaptor.getValue();
+        assertTrue(session.isNtn);
+
+        reset(mPersistAtomsStorage);
+        reset(mServiceState);
+
+        when(mSatelliteController.isInSatelliteModeForCarrierRoaming(any()))
+                .thenReturn(false);
+        mVoiceCallSessionStats0.onImsDial(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        mVoiceCallSessionStats0.onImsCallTerminated(mImsConnection0,
+                new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_NETWORK_NO_LTE_COVERAGE, 0));
+
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        session = callCaptor.getValue();
+        assertFalse(session.isNtn);
+    }
+
+
+    @Test
+    @SmallTest
+    public void singleCall_supportBusinessCall() {
+        PersistableBundle mCarrierConfig = new PersistableBundle();
+        mCarrierConfig.putBoolean(
+                CarrierConfigManager.KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, true);
+        when(mCarrierConfigManager.getConfigForSubId(eq(mPhone.getSubId()),
+                eq(CarrierConfigManager.KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL)))
+                        .thenReturn(mCarrierConfig);
+        setServiceState(mServiceState, TelephonyManager.NETWORK_TYPE_LTE);
+        doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mImsStats).getImsVoiceRadioTech();
+        doReturn(mImsPhone).when(mPhone).getImsPhone();
+        doReturn(false).when(mImsConnection0).isIncoming();
+        doReturn(2000L).when(mImsConnection0).getCreateTime();
+        doReturn(0L).when(mImsConnection0).getDurationMillis();
+        doReturn(mImsCall0).when(mImsConnection0).getCall();
+        doReturn(new ArrayList(List.of(mImsConnection0))).when(mImsCall0).getConnections();
+        doReturn(2).when(mTelephonyManager).getCallComposerStatus();
+        VoiceCallSession expectedCall =
+                makeSlot0CallProto(
+                        VOICE_CALL_SESSION__BEARER_AT_END__CALL_BEARER_IMS,
+                        VOICE_CALL_SESSION__DIRECTION__CALL_DIRECTION_MO,
+                        TelephonyManager.NETWORK_TYPE_LTE,
+                        ImsReasonInfo.CODE_REMOTE_CALL_DECLINE);
+        expectedCall.setupDurationMillis = 200;
+        expectedCall.setupFailed = true;
+        expectedCall.ratAtConnected = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        expectedCall.codecBitmask = 1L << AudioCodec.AUDIO_CODEC_EVS_SWB;
+        expectedCall.mainCodecQuality =
+                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.supportsBusinessCallComposer = true;
+        // 0 is defined as UNKNOWN, adding 1 to original value.
+        expectedCall.callComposerStatus = 3;
+        VoiceCallRatUsage expectedRatUsage =
+                makeRatUsageProto(
+                        CARRIER_ID_SLOT_0, TelephonyManager.NETWORK_TYPE_LTE, 2000L, 12000L, 1L);
+        final AtomicReference<VoiceCallRatUsage[]> ratUsage = setupRatUsageCapture();
+
+        mVoiceCallSessionStats0.setTimeMillis(2000L);
+        doReturn(Call.State.DIALING).when(mImsCall0).getState();
+        doReturn(Call.State.DIALING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onImsDial(mImsConnection0);
+        mVoiceCallSessionStats0.setTimeMillis(2100L);
+        mVoiceCallSessionStats0.onAudioCodecChanged(
+                mImsConnection0, ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB);
+        mVoiceCallSessionStats0.setTimeMillis(2200L);
+        doReturn(Call.State.ALERTING).when(mImsCall0).getState();
+        doReturn(Call.State.ALERTING).when(mImsConnection0).getState();
+        mVoiceCallSessionStats0.onCallStateChanged(mImsCall0);
+        mVoiceCallSessionStats0.setTimeMillis(12000L);
+        mVoiceCallSessionStats0.onImsCallTerminated(
+                mImsConnection0, new ImsReasonInfo(ImsReasonInfo.CODE_REMOTE_CALL_DECLINE, 0));
+
+        ArgumentCaptor<VoiceCallSession> callCaptor =
+                ArgumentCaptor.forClass(VoiceCallSession.class);
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallSession(callCaptor.capture());
+        verify(mPersistAtomsStorage, times(1)).addVoiceCallRatUsage(any());
+        verifyNoMoreInteractions(mPersistAtomsStorage);
+        assertProtoEquals(expectedCall, callCaptor.getValue());
+        assertThat(ratUsage.get()).hasLength(1);
+        assertProtoEquals(expectedRatUsage, ratUsage.get()[0]);
+    }
+
     private AtomicReference<VoiceCallRatUsage[]> setupRatUsageCapture() {
         final AtomicReference<VoiceCallRatUsage[]> ratUsage = new AtomicReference<>(null);
         doAnswer(
@@ -2841,6 +2944,7 @@
         call.setupBeginMillis = 0L;
         call.signalStrengthAtEnd = 2;
         call.vonrEnabled = false;
+        call.callComposerStatus = 1;
         return call;
     }
 
@@ -2876,6 +2980,7 @@
         call.isRoaming = false;
         call.setupBeginMillis = 0L;
         call.signalStrengthAtEnd = 2;
+        call.callComposerStatus = 1;
         return call;
     }
 
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 76cd4ad..8173bbc 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/ControllerMetricsStatsTest.java
@@ -133,36 +133,48 @@
     @Test
     public void testReportOutgoingDatagramSuccessCount() {
         mTestStats.initializeParams();
-        int datagramType = SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE;
-        for (int i = 0; i < 10; i++) {
-            mControllerMetricsStatsUT.reportOutgoingDatagramSuccessCount(datagramType);
+        int[] sosDatagramTypes = {SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
+                SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED};
+        for (int datagramType : sosDatagramTypes) {
+            for (int i = 0; i < 10; i++) {
+                mControllerMetricsStatsUT.reportOutgoingDatagramSuccessCount(datagramType, false);
+            }
+            assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+            assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+            assertEquals(10, mTestStats.mCountOfOutgoingDatagramSuccess);
+            assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
+            assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+            assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+            assertEquals(10, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+            assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
+            assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+            assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+            assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+            assertEquals(0, mTestStats.mCountOfProvisionFail);
+            assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+            assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+            assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+            assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+            assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+            assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsSuccess);
+            assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsFail);
+            assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramSuccess);
+            assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramFail);
+            assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramSuccess);
+            assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramFail);
+            assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveSuccess);
+            assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveFail);
+            mTestStats.initializeParams();
         }
-        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
-        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
-        assertEquals(10, mTestStats.mCountOfOutgoingDatagramSuccess);
-        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
-        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
-        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
-        assertEquals(10, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
-        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
-        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
-        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
-        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
-        assertEquals(0, mTestStats.mCountOfProvisionFail);
-        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
-        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
-        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
-        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
-        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
-        mTestStats.initializeParams();
 
-        datagramType = SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING;
+        int datagramType = SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING;
         for (int i = 0; i < 10; i++) {
-            mControllerMetricsStatsUT.reportOutgoingDatagramSuccessCount(datagramType);
+            mControllerMetricsStatsUT.reportOutgoingDatagramSuccessCount(datagramType, true);
         }
         assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
         assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
-        assertEquals(10, mTestStats.mCountOfOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
         assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
         assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
         assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
@@ -177,11 +189,19 @@
         assertEquals(0, mTestStats.mTotalServiceUptimeSec);
         assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
         assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsFail);
+        assertEquals(10, mTestStats.mCountOfDemoModeOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveFail);
         mTestStats.initializeParams();
 
-        datagramType = SatelliteManager.DATAGRAM_TYPE_UNKNOWN;
+        datagramType = SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE;
         for (int i = 0; i < 10; i++) {
-            mControllerMetricsStatsUT.reportOutgoingDatagramSuccessCount(datagramType);
+            mControllerMetricsStatsUT.reportOutgoingDatagramSuccessCount(datagramType, false);
         }
         assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
         assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
@@ -200,43 +220,63 @@
         assertEquals(0, mTestStats.mTotalServiceUptimeSec);
         assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
         assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramFail);
+        assertEquals(10, mTestStats.mCountOfDatagramTypeKeepAliveSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveFail);
         mTestStats.initializeParams();
     }
 
     @Test
     public void reportOutgoingDatagramFailCount() {
         mTestStats.initializeParams();
-        int datagramType = SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE;
-        for (int i = 0; i < 10; i++) {
-            mControllerMetricsStatsUT.reportOutgoingDatagramFailCount(datagramType);
+        int[] sosDatagramTypes = {SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
+                SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED};
+        for (int datagramType : sosDatagramTypes) {
+            for (int i = 0; i < 10; i++) {
+                mControllerMetricsStatsUT.reportOutgoingDatagramFailCount(datagramType, false);
+            }
+            assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
+            assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
+            assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
+            assertEquals(10, mTestStats.mCountOfOutgoingDatagramFail);
+            assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
+            assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
+            assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
+            assertEquals(10, mTestStats.mCountOfDatagramTypeSosSmsFail);
+            assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
+            assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
+            assertEquals(0, mTestStats.mCountOfProvisionSuccess);
+            assertEquals(0, mTestStats.mCountOfProvisionFail);
+            assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
+            assertEquals(0, mTestStats.mCountOfDeprovisionFail);
+            assertEquals(0, mTestStats.mTotalServiceUptimeSec);
+            assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
+            assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+            assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsSuccess);
+            assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsFail);
+            assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramSuccess);
+            assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramFail);
+            assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramSuccess);
+            assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramFail);
+            assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveSuccess);
+            assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveFail);
+            mTestStats.initializeParams();
         }
-        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
-        assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
-        assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
-        assertEquals(10, mTestStats.mCountOfOutgoingDatagramFail);
-        assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
-        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
-        assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
-        assertEquals(10, mTestStats.mCountOfDatagramTypeSosSmsFail);
-        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
-        assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingFail);
-        assertEquals(0, mTestStats.mCountOfProvisionSuccess);
-        assertEquals(0, mTestStats.mCountOfProvisionFail);
-        assertEquals(0, mTestStats.mCountOfDeprovisionSuccess);
-        assertEquals(0, mTestStats.mCountOfDeprovisionFail);
-        assertEquals(0, mTestStats.mTotalServiceUptimeSec);
-        assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
-        assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
-        mTestStats.initializeParams();
 
-        datagramType = SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING;
+        int datagramType = SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING;
         for (int i = 0; i < 10; i++) {
-            mControllerMetricsStatsUT.reportOutgoingDatagramFailCount(datagramType);
+            mControllerMetricsStatsUT.reportOutgoingDatagramFailCount(datagramType, true);
         }
         assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
         assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
         assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
-        assertEquals(10, mTestStats.mCountOfOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
         assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
         assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
         assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
@@ -250,11 +290,19 @@
         assertEquals(0, mTestStats.mTotalServiceUptimeSec);
         assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
         assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramSuccess);
+        assertEquals(10, mTestStats.mCountOfDemoModeOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveFail);
         mTestStats.initializeParams();
 
-        datagramType = SatelliteManager.DATAGRAM_TYPE_UNKNOWN;
+        datagramType = SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE;
         for (int i = 0; i < 10; i++) {
-            mControllerMetricsStatsUT.reportOutgoingDatagramFailCount(datagramType);
+            mControllerMetricsStatsUT.reportOutgoingDatagramFailCount(datagramType, false);
         }
         assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
         assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
@@ -273,6 +321,14 @@
         assertEquals(0, mTestStats.mTotalServiceUptimeSec);
         assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
         assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveSuccess);
+        assertEquals(10, mTestStats.mCountOfDatagramTypeKeepAliveFail);
         mTestStats.initializeParams();
     }
 
@@ -282,7 +338,7 @@
 
         int result = SatelliteManager.SATELLITE_RESULT_SUCCESS;
         for (int i = 0; i < 10; i++) {
-            mControllerMetricsStatsUT.reportIncomingDatagramCount(result);
+            mControllerMetricsStatsUT.reportIncomingDatagramCount(result, false);
         }
         assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
         assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
@@ -301,18 +357,26 @@
         assertEquals(0, mTestStats.mTotalServiceUptimeSec);
         assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
         assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveFail);
         mTestStats.initializeParams();
 
         result = SatelliteManager.SATELLITE_RESULT_SERVER_ERROR;
         for (int i = 0; i < 10; i++) {
-            mControllerMetricsStatsUT.reportIncomingDatagramCount(result);
+            mControllerMetricsStatsUT.reportIncomingDatagramCount(result, true);
         }
         assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
         assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
         assertEquals(0, mTestStats.mCountOfOutgoingDatagramSuccess);
         assertEquals(0, mTestStats.mCountOfOutgoingDatagramFail);
         assertEquals(0, mTestStats.mCountOfIncomingDatagramSuccess);
-        assertEquals(10, mTestStats.mCountOfIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfIncomingDatagramFail);
         assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsSuccess);
         assertEquals(0, mTestStats.mCountOfDatagramTypeSosSmsFail);
         assertEquals(0, mTestStats.mCountOfDatagramTypeLocationSharingSuccess);
@@ -324,11 +388,19 @@
         assertEquals(0, mTestStats.mTotalServiceUptimeSec);
         assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
         assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramSuccess);
+        assertEquals(10, mTestStats.mCountOfDemoModeIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveFail);
         mTestStats.initializeParams();
 
         result = SatelliteManager.SATELLITE_RESULT_INVALID_TELEPHONY_STATE;
         for (int i = 0; i < 10; i++) {
-            mControllerMetricsStatsUT.reportIncomingDatagramCount(result);
+            mControllerMetricsStatsUT.reportIncomingDatagramCount(result, false);
         }
         assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsSuccess);
         assertEquals(0, mTestStats.mCountOfSatelliteServiceEnablementsFail);
@@ -347,6 +419,14 @@
         assertEquals(0, mTestStats.mTotalServiceUptimeSec);
         assertEquals(0, mTestStats.mTotalBatteryConsumptionPercent);
         assertEquals(0, mTestStats.mTotalBatteryChargedTimeSec);
+        assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeSatelliteServiceEnablementsFail);
+        assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeOutgoingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramSuccess);
+        assertEquals(0, mTestStats.mCountOfDemoModeIncomingDatagramFail);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveSuccess);
+        assertEquals(0, mTestStats.mCountOfDatagramTypeKeepAliveFail);
         mTestStats.initializeParams();
     }
 
@@ -558,6 +638,14 @@
         public int mTotalServiceUptimeSec;
         public int mTotalBatteryConsumptionPercent;
         public int mTotalBatteryChargedTimeSec;
+        private int mCountOfDemoModeSatelliteServiceEnablementsSuccess = 0;
+        private int mCountOfDemoModeSatelliteServiceEnablementsFail = 0;
+        private int mCountOfDemoModeOutgoingDatagramSuccess = 0;
+        private int mCountOfDemoModeOutgoingDatagramFail = 0;
+        private int mCountOfDemoModeIncomingDatagramSuccess = 0;
+        private int mCountOfDemoModeIncomingDatagramFail = 0;
+        private int mCountOfDatagramTypeKeepAliveSuccess = 0;
+        private int mCountOfDatagramTypeKeepAliveFail = 0;
 
         @Override
         public synchronized void onSatelliteControllerMetrics(SatelliteControllerParams param) {
@@ -582,6 +670,18 @@
             mTotalServiceUptimeSec += param.getTotalServiceUptimeSec();
             mTotalBatteryConsumptionPercent += param.getTotalBatteryConsumptionPercent();
             mTotalBatteryChargedTimeSec += param.getTotalBatteryChargedTimeSec();
+            mCountOfDemoModeSatelliteServiceEnablementsSuccess +=
+                    param.getCountOfDemoModeSatelliteServiceEnablementsSuccess();
+            mCountOfDemoModeSatelliteServiceEnablementsFail +=
+                    param.getCountOfDemoModeSatelliteServiceEnablementsFail();
+            mCountOfDemoModeOutgoingDatagramSuccess +=
+                    param.getCountOfDemoModeOutgoingDatagramSuccess();
+            mCountOfDemoModeOutgoingDatagramFail += param.getCountOfDemoModeOutgoingDatagramFail();
+            mCountOfDemoModeIncomingDatagramSuccess +=
+                    param.getCountOfDemoModeIncomingDatagramSuccess();
+            mCountOfDemoModeIncomingDatagramFail += param.getCountOfDemoModeIncomingDatagramFail();
+            mCountOfDatagramTypeKeepAliveSuccess += param.getCountOfDatagramTypeKeepAliveSuccess();
+            mCountOfDatagramTypeKeepAliveFail += param.getCountOfDatagramTypeKeepAliveFail();
         }
 
         public void initializeParams() {
@@ -602,6 +702,14 @@
             mTotalServiceUptimeSec = 0;
             mTotalBatteryConsumptionPercent = 0;
             mTotalBatteryChargedTimeSec = 0;
+            mCountOfDemoModeSatelliteServiceEnablementsSuccess = 0;
+            mCountOfDemoModeSatelliteServiceEnablementsFail = 0;
+            mCountOfDemoModeOutgoingDatagramSuccess = 0;
+            mCountOfDemoModeOutgoingDatagramFail = 0;
+            mCountOfDemoModeIncomingDatagramSuccess = 0;
+            mCountOfDemoModeIncomingDatagramFail = 0;
+            mCountOfDatagramTypeKeepAliveSuccess = 0;
+            mCountOfDatagramTypeKeepAliveFail = 0;
         }
     }
 }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramControllerTest.java
new file mode 100644
index 0000000..2961b4d
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramControllerTest.java
@@ -0,0 +1,267 @@
+/*
+ * 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.satellite;
+
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE;
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED;
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP;
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING;
+import static android.telephony.satellite.SatelliteManager.DATAGRAM_TYPE_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_RECEIVING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Looper;
+import android.telephony.satellite.SatelliteDatagram;
+import android.telephony.satellite.SatelliteManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DatagramControllerTest extends TelephonyTest {
+    private static final String TAG = "DatagramControllerTest";
+
+    private DatagramController mDatagramControllerUT;
+
+    @Mock private DatagramReceiver mMockDatagramReceiver;
+    @Mock private DatagramDispatcher mMockDatagramDispatcher;
+    @Mock private PointingAppController mMockPointingAppController;
+    @Mock private SatelliteSessionController mMockSatelliteSessionController;
+    @Mock private SatelliteController mMockSatelliteController;
+
+    private static final int SUB_ID = 0;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+        logd(TAG + " Setup!");
+
+        replaceInstance(DatagramDispatcher.class, "sInstance", null,
+                mMockDatagramDispatcher);
+        replaceInstance(DatagramReceiver.class, "sInstance", null,
+                mMockDatagramReceiver);
+        replaceInstance(SatelliteController.class, "sInstance", null,
+                mMockSatelliteController);
+        replaceInstance(SatelliteSessionController.class, "sInstance", null,
+                mMockSatelliteSessionController);
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+        mDatagramControllerUT = new DatagramController(
+                mContext, Looper.myLooper(), mFeatureFlags, mMockPointingAppController);
+
+        // 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);
+        pushDemoModeSosDatagram(DATAGRAM_TYPE_SOS_MESSAGE);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        logd(TAG + " tearDown");
+        super.tearDown();
+    }
+
+    @Test
+    public void testUpdateSendStatus() throws Exception {
+        testUpdateSendStatus(true, DATAGRAM_TYPE_SOS_MESSAGE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING);
+        testUpdateSendStatus(true, DATAGRAM_TYPE_LOCATION_SHARING,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        testUpdateSendStatus(false, DATAGRAM_TYPE_KEEP_ALIVE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING);
+        pushDemoModeSosDatagram(DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP);
+        testUpdateSendStatus(true, DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING);
+        pushDemoModeSosDatagram(DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED);
+        testUpdateSendStatus(true, DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING);
+    }
+
+    @Test
+    public void testUpdateReceiveStatus() throws Exception {
+        testUpdateReceiveStatus(true, SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+        testUpdateReceiveStatus(true, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        testUpdateReceiveStatus(false, SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING);
+    }
+
+    @Test
+    public void testSetDeviceAlignedWithSatellite() throws Exception {
+        testSetDeviceAlignedWithSatellite(true);
+        testSetDeviceAlignedWithSatellite(false);
+    }
+
+    @Test
+    public void testSuppressSendStatusUpdate() throws Exception {
+        // Move to NOT_CONNECTED state
+        mDatagramControllerUT.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+
+        clearInvocations(mMockSatelliteSessionController);
+        clearInvocations(mMockPointingAppController);
+        clearInvocations(mMockDatagramReceiver);
+
+        int sendPendingCount = 1;
+        int errorCode = SATELLITE_RESULT_SUCCESS;
+        mDatagramControllerUT.updateSendStatus(SUB_ID, DATAGRAM_TYPE_KEEP_ALIVE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, sendPendingCount, errorCode);
+        verifyZeroInteractions(mMockSatelliteSessionController);
+        verifyZeroInteractions(mMockPointingAppController);
+        verifyZeroInteractions(mMockDatagramReceiver);
+    }
+
+    @Test
+    public void testNeedsWaitingForSatelliteConnected() throws Exception {
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(false);
+        assertFalse(mDatagramControllerUT
+                .needsWaitingForSatelliteConnected(DATAGRAM_TYPE_KEEP_ALIVE));
+
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+
+        int[] sosDatagramTypes = {DATAGRAM_TYPE_SOS_MESSAGE,
+                DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
+                DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED};
+        for (int datagramType : sosDatagramTypes) {
+            mDatagramControllerUT.onSatelliteModemStateChanged(
+                    SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+            assertFalse(mDatagramControllerUT
+                    .needsWaitingForSatelliteConnected(DATAGRAM_TYPE_KEEP_ALIVE));
+            assertTrue(mDatagramControllerUT
+                    .needsWaitingForSatelliteConnected(datagramType));
+
+            mDatagramControllerUT.onSatelliteModemStateChanged(
+                    SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+            assertFalse(mDatagramControllerUT
+                    .needsWaitingForSatelliteConnected(datagramType));
+
+            mDatagramControllerUT.onSatelliteModemStateChanged(
+                    SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+            assertFalse(mDatagramControllerUT
+                    .needsWaitingForSatelliteConnected(datagramType));
+
+            mDatagramControllerUT.onSatelliteModemStateChanged(
+                    SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+            assertTrue(mDatagramControllerUT
+                    .needsWaitingForSatelliteConnected(datagramType));
+        }
+    }
+
+    private void testUpdateSendStatus(boolean isDemoMode, int datagramType, int sendState) {
+        mDatagramControllerUT.setDemoMode(isDemoMode);
+        clearAllInvocations();
+
+        int sendPendingCount = 1;
+        int errorCode = SATELLITE_RESULT_SUCCESS;
+        mDatagramControllerUT.updateSendStatus(SUB_ID, datagramType, sendState, sendPendingCount,
+                errorCode);
+
+        verify(mMockSatelliteSessionController)
+                .onDatagramTransferStateChanged(eq(sendState), anyInt());
+        verify(mMockPointingAppController).updateSendDatagramTransferState(
+                eq(SUB_ID), eq(datagramType), eq(sendState), eq(sendPendingCount), eq(errorCode));
+
+        if (isDemoMode) {
+            if (sendState == SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE) {
+                verify(mMockDatagramReceiver).pollPendingSatelliteDatagrams(
+                        anyInt(), any(Consumer.class));
+            }
+        } else {
+            verify(mMockDatagramReceiver, never()).pollPendingSatelliteDatagrams(
+                    anyInt(), any(Consumer.class));
+        }
+    }
+
+    private void testUpdateReceiveStatus(boolean isDemoMode, int receiveState) {
+        mDatagramControllerUT.setDemoMode(isDemoMode);
+        clearAllInvocations();
+
+        int receivePendingCount = 1;
+        int errorCode = SATELLITE_RESULT_SUCCESS;
+        mDatagramControllerUT.updateReceiveStatus(
+                SUB_ID, receiveState, receivePendingCount, errorCode);
+
+        verify(mMockSatelliteSessionController)
+                .onDatagramTransferStateChanged(anyInt(), eq(receiveState));
+        verify(mMockPointingAppController).updateReceiveDatagramTransferState(
+                eq(SUB_ID), eq(receiveState), eq(receivePendingCount), eq(errorCode));
+
+        if (isDemoMode && receiveState == SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE) {
+            verify(mMockDatagramReceiver).pollPendingSatelliteDatagrams(
+                    anyInt(), any(Consumer.class));
+        } else {
+            verify(mMockDatagramReceiver, never()).pollPendingSatelliteDatagrams(
+                    anyInt(), any(Consumer.class));
+        }
+    }
+
+    private void testSetDeviceAlignedWithSatellite(boolean isAligned) {
+        mDatagramControllerUT.setDemoMode(true);
+        clearAllInvocations();
+
+        mDatagramControllerUT.setDeviceAlignedWithSatellite(isAligned);
+        verify(mMockDatagramDispatcher).setDeviceAlignedWithSatellite(eq(isAligned));
+        verify(mMockDatagramReceiver).setDeviceAlignedWithSatellite(eq(isAligned));
+        if (isAligned) {
+            verify(mMockDatagramReceiver).pollPendingSatelliteDatagrams(
+                    anyInt(), any(Consumer.class));
+        } else {
+            verify(mMockDatagramReceiver, never()).pollPendingSatelliteDatagrams(
+                    anyInt(), any(Consumer.class));
+        }
+    }
+
+    private void clearAllInvocations() {
+        clearInvocations(mMockSatelliteSessionController);
+        clearInvocations(mMockPointingAppController);
+        clearInvocations(mMockDatagramReceiver);
+        clearInvocations(mMockDatagramDispatcher);
+    }
+
+    private void pushDemoModeSosDatagram(int datagramType) {
+        String testMessage = "This is a test datagram message";
+        SatelliteDatagram datagram = new SatelliteDatagram(testMessage.getBytes());
+        mDatagramControllerUT.setDemoMode(true);
+        mDatagramControllerUT.pushDemoModeDatagram(datagramType, datagram);
+    }
+}
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 37ff69a..7094399 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramDispatcherTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.satellite;
 
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_TIMEOUT;
+import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
 import static com.android.internal.telephony.satellite.DatagramController.SATELLITE_ALIGN_TIMEOUT;
 
@@ -59,7 +60,9 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
+import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
 
 import org.junit.After;
 import org.junit.Before;
@@ -83,21 +86,34 @@
     private static final int SUB_ID = 0;
     private static final int DATAGRAM_TYPE1 = SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE;
     private static final int DATAGRAM_TYPE2 = SatelliteManager.DATAGRAM_TYPE_LOCATION_SHARING;
+    private static final int DATAGRAM_TYPE3 = SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE;
+    private static final int DATAGRAM_TYPE4 =
+            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 String TEST_MESSAGE = "This is a test datagram message";
     private static final long TEST_EXPIRE_TIMER_SATELLITE_ALIGN = TimeUnit.SECONDS.toMillis(1);
     private static final int TEST_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMEOUT_MILLIS =
             (int) TimeUnit.SECONDS.toMillis(180);
     private static final long TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS =
             TimeUnit.SECONDS.toMillis(60);
+    private static final Long TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE = TimeUnit.SECONDS.toMillis(10);
+    private static final long
+            TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_FOR_LAST_MESSAGE_TIMEOUT_MILLIS =
+            TimeUnit.SECONDS.toMillis(60);
+    private static final int
+            TEST_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_FOR_LAST_MESSAGE_TIMEOUT_MILLIS =
+            (int) TimeUnit.SECONDS.toMillis(60);
 
-    private DatagramDispatcher mDatagramDispatcherUT;
-    private TestDatagramDispatcher mTestDemoModeDatagramDispatcher;
+    private TestDatagramDispatcher mDatagramDispatcherUT;
 
     @Mock private DatagramController mMockDatagramController;
     @Mock private DatagramReceiver mMockDatagramReceiver;
     @Mock private SatelliteModemInterface mMockSatelliteModemInterface;
     @Mock private ControllerMetricsStats mMockControllerMetricsStats;
     @Mock private SatelliteSessionController mMockSatelliteSessionController;
+    @Mock private SessionMetricsStats mMockSessionMetricsStats;
 
     /** Variables required to send datagram in the unit tests. */
     LinkedBlockingQueue<Integer> mResultListener;
@@ -136,11 +152,13 @@
                 mMockControllerMetricsStats);
         replaceInstance(SatelliteSessionController.class, "sInstance", null,
                 mMockSatelliteSessionController);
+        replaceInstance(SessionMetricsStats.class, "sInstance", null,
+                mMockSessionMetricsStats);
 
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
-        mDatagramDispatcherUT = DatagramDispatcher.make(mContext, Looper.myLooper(),
-                mMockDatagramController);
-        mTestDemoModeDatagramDispatcher = new TestDatagramDispatcher(mContext, Looper.myLooper(),
+        when(mFeatureFlags.satellitePersistentLogging()).thenReturn(true);
+        mDatagramDispatcherUT = new TestDatagramDispatcher(mContext, Looper.myLooper(),
+                mFeatureFlags,
                 mMockDatagramController);
 
         mResultListener = new LinkedBlockingQueue<>(1);
@@ -154,7 +172,6 @@
         logd(TAG + " tearDown");
         mDatagramDispatcherUT.destroy();
         mDatagramDispatcherUT = null;
-        mTestDemoModeDatagramDispatcher = null;
         mResultListener = null;
         mDatagram = null;
         mInOrder = null;
@@ -172,185 +189,238 @@
             return null;
         }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
                 anyBoolean(), anyBoolean(), any(Message.class));
-        doReturn(true).when(mMockDatagramController)
-                .needsWaitingForSatelliteConnected();
-        when(mMockDatagramController.getDatagramWaitTimeForConnectedState())
-                .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
-        mResultListener.clear();
 
-        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
-                true, mResultListener::offer);
-        processAllMessages();
-        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
-        mInOrder.verify(mMockDatagramController).updateSendStatus(eq(SUB_ID),
-                eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT), eq(1),
-                eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState();
-        verifyZeroInteractions(mMockSatelliteModemInterface);
-        assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+        int[] sosDatagramTypes = {DATAGRAM_TYPE1, DATAGRAM_TYPE4, DATAGRAM_TYPE5};
+        for (int datagramType : sosDatagramTypes) {
+            clearInvocations(mMockDatagramController);
+            clearInvocations(mMockSessionMetricsStats);
+            clearInvocations(mMockSatelliteModemInterface);
+            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);
+            mResultListener.clear();
 
-        mDatagramDispatcherUT.onSatelliteModemStateChanged(
-                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
-        processAllMessages();
+            mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, datagramType, mDatagram,
+                    true, mResultListener::offer);
+            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(mMockSatelliteModemInterface);
+            assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
 
-        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        verifyNoMoreInteractions(mMockDatagramController);
-        verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
-                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
-        assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+            doReturn(false).when(mMockDatagramController)
+                    .needsWaitingForSatelliteConnected(eq(datagramType));
+            mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                    SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+            processAllMessages();
 
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_SUCCESS);
+            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));
+            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);
+            verify(mMockSessionMetricsStats, times(1))
+                    .addCountOfSuccessfulOutgoingDatagram(eq(datagramType));
+            verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
+                    any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+            assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
 
-        clearInvocations(mMockSatelliteModemInterface);
-        clearInvocations(mMockDatagramController);
-        mResultListener.clear();
+            assertThat(mResultListener.peek()).isEqualTo(SATELLITE_RESULT_SUCCESS);
 
-        clearInvocations(mMockSatelliteModemInterface);
-        clearInvocations(mMockDatagramController);
-        mResultListener.clear();
-        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
-                true, mResultListener::offer);
-        processAllMessages();
-        verifyZeroInteractions(mMockSatelliteModemInterface);
-        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
-        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState();
-        assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+            clearInvocations(mMockSatelliteModemInterface);
+            clearInvocations(mMockDatagramController);
+            clearInvocations(mMockSessionMetricsStats);
+            mResultListener.clear();
+            doReturn(true).when(mMockDatagramController)
+                    .needsWaitingForSatelliteConnected(eq(datagramType));
+            mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, datagramType, mDatagram,
+                    true, mResultListener::offer);
+            processAllMessages();
+            verifyZeroInteractions(mMockSatelliteModemInterface);
+            mInOrder.verify(mMockDatagramController)
+                    .needsWaitingForSatelliteConnected(eq(datagramType));
+            mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState(
+                    eq(SatelliteServiceUtils.isLastSosMessage(datagramType)));
+            assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
 
-        moveTimeForward(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
-        processAllMessages();
-        verifyZeroInteractions(mMockSatelliteModemInterface);
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
-                        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),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        assertEquals(1, mResultListener.size());
-        assertThat(mResultListener.peek()).isEqualTo(
-                SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
-        assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+            moveTimeForward(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
+            processAllMessages();
+            verifyZeroInteractions(mMockSatelliteModemInterface);
+            mInOrder.verify(mMockDatagramController)
+                    .updateSendStatus(eq(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID),
+                            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),
+                            eq(datagramType),
+                            eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                            eq(SATELLITE_RESULT_SUCCESS));
+            assertEquals(1, mResultListener.size());
+            assertThat(mResultListener.peek()).isEqualTo(
+                    SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+            assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+            verify(mMockSessionMetricsStats, times(1))
+                    .addCountOfFailedOutgoingDatagram(anyInt(), anyInt());
 
-        mResultListener.clear();
-        mDatagramDispatcherUT.onSatelliteModemStateChanged(
-                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
-        processAllMessages();
-        verifyZeroInteractions(mMockSatelliteModemInterface);
-        assertEquals(0, mResultListener.size());
+            mResultListener.clear();
+            mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                    SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+            processAllMessages();
+            verifyZeroInteractions(mMockSatelliteModemInterface);
+            assertEquals(0, mResultListener.size());
 
-        clearInvocations(mMockSatelliteModemInterface);
-        clearInvocations(mMockDatagramController);
-        mResultListener.clear();
-        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
-                true, mResultListener::offer);
-        processAllMessages();
-        verifyZeroInteractions(mMockSatelliteModemInterface);
-        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
-        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState();
-        assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
-        assertEquals(0, mResultListener.size());
+            clearInvocations(mMockSatelliteModemInterface);
+            clearInvocations(mMockDatagramController);
+            clearInvocations(mMockSessionMetricsStats);
+            mResultListener.clear();
+            mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, datagramType, mDatagram,
+                    true, mResultListener::offer);
+            processAllMessages();
+            verifyZeroInteractions(mMockSatelliteModemInterface);
+            mInOrder.verify(mMockDatagramController)
+                    .needsWaitingForSatelliteConnected(eq(datagramType));
+            mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState(
+                    eq(SatelliteServiceUtils.isLastSosMessage(datagramType)));
+            assertTrue(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+            assertEquals(0, mResultListener.size());
 
-        mDatagramDispatcherUT.onSatelliteModemStateChanged(
-                SatelliteManager.SATELLITE_MODEM_STATE_OFF);
-        processAllMessages();
-        verifyZeroInteractions(mMockSatelliteModemInterface);
-        assertEquals(1, mResultListener.size());
-        assertThat(mResultListener.peek()).isEqualTo(
-                SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
-        assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+            mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                    SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+            processAllMessages();
+            verifyZeroInteractions(mMockSatelliteModemInterface);
+            assertEquals(1, mResultListener.size());
+            assertThat(mResultListener.peek()).isEqualTo(
+                    SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED);
+            assertFalse(mDatagramDispatcherUT.isDatagramWaitForConnectedStateTimerStarted());
+            verify(mMockSessionMetricsStats, times(1))
+                    .addCountOfFailedOutgoingDatagram(anyInt(), anyInt());
+        }
     }
 
     @Test
     public void testSendSatelliteDatagram_timeout() throws  Exception {
-        doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[3];
-
-            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
-                            new AsyncResult(message.obj, null, null))
-                    .sendToTarget();
-
-            // DatagramDispatcher should ignore the second EVENT_SEND_SATELLITE_DATAGRAM_DONE
-            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
-                            new AsyncResult(message.obj, null, null))
-                    .sendToTarget();
-
-            return null;
-        }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
-                anyBoolean(), anyBoolean(), any(Message.class));
-        doReturn(false).when(mMockDatagramController)
-                .needsWaitingForSatelliteConnected();
-        when(mMockDatagramController.getDatagramWaitTimeForConnectedState())
+        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);
         mContextFixture.putIntResource(
                 R.integer.config_wait_for_datagram_sending_response_timeout_millis,
                 TEST_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_TIMEOUT_MILLIS);
+        mContextFixture.putIntResource(
+                R.integer.config_wait_for_datagram_sending_response_for_last_message_timeout_millis,
+                TEST_WAIT_FOR_DATAGRAM_SENDING_RESPONSE_FOR_LAST_MESSAGE_TIMEOUT_MILLIS);
         mResultListener.clear();
+        int[] sosDatagramTypes = {DATAGRAM_TYPE1, DATAGRAM_TYPE4, DATAGRAM_TYPE5};
+        for (int datagramType : sosDatagramTypes) {
+            doAnswer(invocation -> {
+                Message message = (Message) invocation.getArguments()[3];
 
-        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
-                true, mResultListener::offer);
-        processAllMessages();
-        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
-        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        verifyNoMoreInteractions(mMockDatagramController);
-        verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
-                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_SUCCESS);
+                mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                                new AsyncResult(message.obj, null, null))
+                        .sendToTarget();
 
-        clearInvocations(mMockSatelliteModemInterface);
-        clearInvocations(mMockDatagramController);
-        mResultListener.clear();
+                // DatagramDispatcher should ignore the second EVENT_SEND_SATELLITE_DATAGRAM_DONE
+                mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+                                new AsyncResult(message.obj, null, null))
+                        .sendToTarget();
 
-        // No response for the send request from modem
-        doNothing().when(mMockSatelliteModemInterface).sendSatelliteDatagram(
-                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+                return null;
+            }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(
+                    any(SatelliteDatagram.class),
+                    anyBoolean(), anyBoolean(), any(Message.class));
+            doReturn(false).when(mMockDatagramController)
+                    .needsWaitingForSatelliteConnected(eq(datagramType));
 
-        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
-                true, mResultListener::offer);
-        processAllMessages();
-        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
-        mInOrder.verify(mMockDatagramController).isPollingInIdleState();
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), eq(1),
-                        eq(SATELLITE_RESULT_MODEM_TIMEOUT));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        verifyNoMoreInteractions(mMockDatagramController);
-        verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
-                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
-        verify(mMockSatelliteModemInterface).abortSendingSatelliteDatagrams(any(Message.class));
-        assertThat(mResultListener.peek()).isEqualTo(SATELLITE_RESULT_MODEM_TIMEOUT);
+            mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, datagramType, mDatagram,
+                    true, mResultListener::offer);
+            processAllMessages();
+            mInOrder.verify(mMockDatagramController)
+                    .needsWaitingForSatelliteConnected(eq(datagramType));
+            mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+            mInOrder.verify(mMockDatagramController)
+                    .updateSendStatus(eq(SUB_ID), eq(datagramType),
+                            eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                            eq(SATELLITE_RESULT_SUCCESS));
+            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);
+            verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
+                    any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+            assertThat(mResultListener.peek()).isEqualTo(SATELLITE_RESULT_SUCCESS);
+            verify(mMockSessionMetricsStats, times(1))
+                    .addCountOfSuccessfulOutgoingDatagram(anyInt());
+            clearInvocations(mMockSatelliteModemInterface);
+            clearInvocations(mMockDatagramController);
+            clearInvocations(mMockSessionMetricsStats);
+            mResultListener.clear();
+
+            // No response for the send request from modem
+            doNothing().when(mMockSatelliteModemInterface).sendSatelliteDatagram(
+                    any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+
+            mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, datagramType, mDatagram,
+                    true, mResultListener::offer);
+            processAllMessages();
+            mInOrder.verify(mMockDatagramController)
+                    .needsWaitingForSatelliteConnected(eq(datagramType));
+            mInOrder.verify(mMockDatagramController).isPollingInIdleState();
+            mInOrder.verify(mMockDatagramController)
+                    .updateSendStatus(eq(SUB_ID), eq(datagramType),
+                            eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                            eq(SATELLITE_RESULT_SUCCESS));
+            mInOrder.verify(mMockDatagramController)
+                    .updateSendStatus(eq(SUB_ID), eq(datagramType),
+                            eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED),
+                            eq(1),
+                            eq(SATELLITE_RESULT_MODEM_TIMEOUT));
+            mInOrder.verify(mMockDatagramController)
+                    .updateSendStatus(eq(SUB_ID), eq(datagramType),
+                            eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                            eq(SATELLITE_RESULT_SUCCESS));
+            verifyNoMoreInteractions(mMockDatagramController);
+            verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
+                    any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+            verify(mMockSatelliteModemInterface).abortSendingSatelliteDatagrams(any(Message.class));
+            assertThat(mResultListener.peek()).isEqualTo(SATELLITE_RESULT_MODEM_TIMEOUT);
+            verify(mMockSessionMetricsStats, times(1))
+                    .addCountOfFailedOutgoingDatagram(anyInt(), anyInt());
+
+            clearInvocations(mMockSatelliteModemInterface);
+            clearInvocations(mMockDatagramController);
+            clearInvocations(mMockSessionMetricsStats);
+            mResultListener.clear();
+        }
     }
 
     @Test
@@ -372,98 +442,123 @@
 
         processAllMessages();
 
-        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
+        mInOrder.verify(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(DATAGRAM_TYPE2));
         mInOrder.verify(mMockDatagramController).isPollingInIdleState();
         mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
+                .updateSendStatus(eq(SUB_ID), eq(DATAGRAM_TYPE2),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+                        eq(SATELLITE_RESULT_SUCCESS));
         mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
+                .updateSendStatus(eq(SUB_ID), eq(DATAGRAM_TYPE2),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED), eq(0),
                         eq(SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR));
         mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
+                .updateSendStatus(eq(SUB_ID), eq(DATAGRAM_TYPE2),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+                        eq(SATELLITE_RESULT_SUCCESS));
         verifyNoMoreInteractions(mMockDatagramController);
 
         assertThat(mResultListener.peek()).isEqualTo(
                 SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
+        verify(mMockSessionMetricsStats, times(1))
+                .addCountOfFailedOutgoingDatagram(anyInt(), anyInt());
     }
 
     @Test
     public void testSendSatelliteDatagram_DemoMode_Align_Success() throws Exception {
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[3];
-            mTestDemoModeDatagramDispatcher.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
                             new AsyncResult(message.obj, null, null))
                     .sendToTarget();
             return null;
         }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
                 anyBoolean(), anyBoolean(), any(Message.class));
-        mTestDemoModeDatagramDispatcher.setDemoMode(true);
-        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(true);
+        mDatagramDispatcherUT.setDemoMode(true);
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(true);
 
-        mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
-                true, mResultListener::offer);
+        int[] sosDatagramTypes = {DATAGRAM_TYPE1, DATAGRAM_TYPE4, DATAGRAM_TYPE5};
+        for (int datagramType : sosDatagramTypes) {
+            clearInvocations(mMockDatagramController);
+            clearInvocations(mMockSessionMetricsStats);
+            mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, datagramType, mDatagram,
+                    true, mResultListener::offer);
 
-        processAllMessages();
+            processAllMessages();
+            moveTimeForward(TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE);
+            processAllMessages();
 
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_SUCCESS);
-        mTestDemoModeDatagramDispatcher.setDemoMode(false);
-        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(false);
+            mInOrder.verify(mMockDatagramController)
+                    .updateSendStatus(eq(SUB_ID), eq(datagramType),
+                            eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                            eq(SATELLITE_RESULT_SUCCESS));
+            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));
+            assertThat(mResultListener.peek()).isEqualTo(SATELLITE_RESULT_SUCCESS);
+            verify(mMockSessionMetricsStats, times(1))
+                    .addCountOfSuccessfulOutgoingDatagram(eq(datagramType));
+            mDatagramDispatcherUT.setDemoMode(false);
+            mDatagramDispatcherUT.setDeviceAlignedWithSatellite(false);
+        }
     }
 
     @Test
     public void testSendSatelliteDatagram_DemoMode_Align_failed() throws Exception {
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[3];
-            mTestDemoModeDatagramDispatcher.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
+            mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
                             new AsyncResult(message.obj, null, null))
                     .sendToTarget();
             return null;
         }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
                 anyBoolean(), anyBoolean(), any(Message.class));
 
-        long previousTimer = mTestDemoModeDatagramDispatcher.getSatelliteAlignedTimeoutDuration();
-        mTestDemoModeDatagramDispatcher.setDemoMode(true);
-        mTestDemoModeDatagramDispatcher.setDuration(TEST_EXPIRE_TIMER_SATELLITE_ALIGN);
-        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(false);
+        long previousTimer = mDatagramDispatcherUT.getSatelliteAlignedTimeoutDuration();
+        mDatagramDispatcherUT.setDemoMode(true);
+        mDatagramDispatcherUT.setDuration(TEST_EXPIRE_TIMER_SATELLITE_ALIGN);
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(false);
+        when(mMockDatagramController.waitForAligningToSatellite(false)).thenReturn(true);
 
-        mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
-                true, mResultListener::offer);
+        int[] sosDatagramTypes = {DATAGRAM_TYPE1, DATAGRAM_TYPE4, DATAGRAM_TYPE5};
+        for (int datagramType : sosDatagramTypes) {
+            clearInvocations(mMockDatagramController);
+            clearInvocations(mMockSessionMetricsStats);
+            mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, datagramType, mDatagram,
+                    true, mResultListener::offer);
 
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        processAllFutureMessages();
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED),
-                        anyInt(), eq(SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        assertThat(mResultListener.peek()).isEqualTo(
-                SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
-        mTestDemoModeDatagramDispatcher.setDemoMode(false);
-        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(false);
-        mTestDemoModeDatagramDispatcher.setDuration(previousTimer);
+            mInOrder.verify(mMockDatagramController)
+                    .updateSendStatus(eq(SUB_ID), eq(datagramType),
+                            eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
+                            eq(SATELLITE_RESULT_SUCCESS));
+            processAllFutureMessages();
+            mInOrder.verify(mMockDatagramController)
+                    .updateSendStatus(eq(SUB_ID), eq(datagramType),
+                            eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED),
+                            anyInt(), eq(SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE));
+            mInOrder.verify(mMockDatagramController)
+                    .updateSendStatus(eq(SUB_ID), eq(datagramType),
+                            eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
+                            eq(SATELLITE_RESULT_SUCCESS));
+            assertThat(mResultListener.peek()).isEqualTo(
+                    SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+            verify(mMockDatagramController, never()).pollPendingSatelliteDatagrams(anyInt(), any());
+            verify(mMockDatagramController, never()).pushDemoModeDatagram(
+                    anyInt(), any(SatelliteDatagram.class));
+            verify(mMockSessionMetricsStats, times(1))
+                    .addCountOfFailedOutgoingDatagram(anyInt(), anyInt());
+        }
+
+        mDatagramDispatcherUT.setDemoMode(false);
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(false);
+        mDatagramDispatcherUT.setDuration(previousTimer);
     }
 
     @Test
@@ -471,52 +566,61 @@
         doAnswer(invocation -> {
             Message message = (Message) invocation.getArguments()[3];
             mDatagramDispatcherUT.obtainMessage(2 /*EVENT_SEND_SATELLITE_DATAGRAM_DONE*/,
-                    new AsyncResult(message.obj, SatelliteManager.SATELLITE_RESULT_SUCCESS,
+                    new AsyncResult(message.obj, SATELLITE_RESULT_SUCCESS,
                             null)).sendToTarget();
             return null;
         }).when(mMockSatelliteModemInterface).sendSatelliteDatagram(any(SatelliteDatagram.class),
                 anyBoolean(), anyBoolean(), any(Message.class));
-        mTestDemoModeDatagramDispatcher.setDemoMode(true);
-        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(true);
+        mDatagramDispatcherUT.setDemoMode(true);
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(true);
         replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[] {mPhone});
 
-        mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE2, mDatagram,
+        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE2, mDatagram,
                 true, mResultListener::offer);
 
         processAllMessages();
+        moveTimeForward(TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE);
+        processAllMessages();
 
         mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
+                .updateSendStatus(eq(SUB_ID), eq(DATAGRAM_TYPE2),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING), eq(1),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+                        eq(SATELLITE_RESULT_SUCCESS));
 
-        assertThat(mResultListener.peek()).isEqualTo(SatelliteManager.SATELLITE_RESULT_SUCCESS);
+        assertThat(mResultListener.peek()).isEqualTo(SATELLITE_RESULT_SUCCESS);
 
         mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
+                .updateSendStatus(eq(SUB_ID), eq(DATAGRAM_TYPE2),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS), eq(0),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+                        eq(SATELLITE_RESULT_SUCCESS));
 
         mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(eq(SUB_ID),
+                .updateSendStatus(eq(SUB_ID), eq(DATAGRAM_TYPE2),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE), eq(0),
-                        eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+                        eq(SATELLITE_RESULT_SUCCESS));
+        verify(mMockSessionMetricsStats, times(1))
+                .addCountOfSuccessfulOutgoingDatagram(eq(DATAGRAM_TYPE2));
 
-        mTestDemoModeDatagramDispatcher.setDemoMode(false);
-        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(false);
+        mDatagramDispatcherUT.setDemoMode(false);
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(false);
     }
 
     @Test
     public void testSatelliteModemBusy_modemPollingDatagram_sendingDelayed() {
         when(mMockDatagramController.isPollingInIdleState()).thenReturn(false);
 
-        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
-                true, mResultListener::offer);
-        processAllMessages();
-        // As modem is busy receiving datagrams, sending datagram did not proceed further.
-        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
-        mInOrder.verify(mMockDatagramController, times(2)).isPollingInIdleState();
-        verifyNoMoreInteractions(mMockDatagramController);
+        int[] sosDatagramTypes = {DATAGRAM_TYPE1, DATAGRAM_TYPE4, DATAGRAM_TYPE5};
+        for (int datagramType : sosDatagramTypes) {
+            clearInvocations(mMockDatagramController);
+            mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, datagramType, mDatagram,
+                    true, mResultListener::offer);
+            processAllMessages();
+            // As modem is busy receiving datagrams, sending datagram did not proceed further.
+            mInOrder.verify(mMockDatagramController)
+                    .needsWaitingForSatelliteConnected(eq(datagramType));
+            mInOrder.verify(mMockDatagramController, times(2)).isPollingInIdleState();
+            verifyNoMoreInteractions(mMockDatagramController);
+        }
     }
 
     @Test
@@ -529,22 +633,26 @@
 
     @Test
     public void testOnSatelliteModemStateChanged_modemStateOff_modemSendingDatagrams() {
-        mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
-                true, mResultListener::offer);
+        int[] sosDatagramTypes = {DATAGRAM_TYPE1, DATAGRAM_TYPE4, DATAGRAM_TYPE5};
+        for (int datagramType : sosDatagramTypes) {
+            clearInvocations(mMockDatagramController);
+            mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, datagramType, mDatagram,
+                    true, mResultListener::offer);
 
-        mDatagramDispatcherUT.onSatelliteModemStateChanged(
-                SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+            mDatagramDispatcherUT.onSatelliteModemStateChanged(
+                    SatelliteManager.SATELLITE_MODEM_STATE_OFF);
 
-        processAllMessages();
+            processAllMessages();
 
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(anyInt(),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED),
-                        eq(1), eq(SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED));
-        mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(anyInt(),
-                        eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
-                        eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+            mInOrder.verify(mMockDatagramController)
+                    .updateSendStatus(anyInt(), eq(datagramType),
+                            eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_FAILED),
+                            eq(1), eq(SatelliteManager.SATELLITE_RESULT_REQUEST_ABORTED));
+            mInOrder.verify(mMockDatagramController)
+                    .updateSendStatus(anyInt(), eq(datagramType),
+                            eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
+                            eq(0), eq(SATELLITE_RESULT_SUCCESS));
+        }
     }
 
     @Test
@@ -555,70 +663,107 @@
         processAllMessages();
 
         mInOrder.verify(mMockDatagramController)
-                .updateSendStatus(anyInt(),
+                .updateSendStatus(anyInt(), anyInt(),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
-                        eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+                        eq(0), eq(SATELLITE_RESULT_SUCCESS));
     }
 
     @Test
-    public void testSendSatelliteDatagramToModemInDemoMode()
-            throws Exception {
+    public void testSendSatelliteDatagramToModemInDemoMode() throws Exception {
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
 
-        doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[3];
-            mTestDemoModeDatagramDispatcher.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));
-        mTestDemoModeDatagramDispatcher.setDemoMode(true);
-        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(true);
-        mIntegerConsumerSemaphore.drainPermits();
+        mDatagramDispatcherUT.setDemoMode(true);
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(true);
 
-        // Test when overlay config config_send_satellite_datagram_to_modem_in_demo_mode is true
-        mTestDemoModeDatagramDispatcher.setShouldSendDatagramToModemInDemoMode(null);
-        mContextFixture.putBooleanResource(mConfigSendSatelliteDatagramToModemInDemoMode, true);
-        mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
-                true, mIntegerConsumer);
-        processAllMessages();
-        waitForIntegerConsumerResult(1);
-        assertEquals(SatelliteManager.SATELLITE_RESULT_SUCCESS,
-                (int) mIntegerConsumerResult.get(0));
-        mIntegerConsumerResult.clear();
-        verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
-                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+        int[] sosDatagramTypes = {DATAGRAM_TYPE1, DATAGRAM_TYPE4, DATAGRAM_TYPE5};
+        for (int datagramType : sosDatagramTypes) {
+            mIntegerConsumerSemaphore.drainPermits();
+            mIntegerConsumerResult.clear();
+            clearInvocations(mMockDatagramController);
+            clearInvocations(mMockSatelliteModemInterface);
+            clearInvocations(mMockSessionMetricsStats);
+            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));
 
-        // Test when overlay config config_send_satellite_datagram_to_modem_in_demo_mode is false
-        reset(mMockSatelliteModemInterface);
-        mTestDemoModeDatagramDispatcher.setShouldSendDatagramToModemInDemoMode(null);
-        mContextFixture.putBooleanResource(mConfigSendSatelliteDatagramToModemInDemoMode, false);
-        mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
-                true, mIntegerConsumer);
-        processAllMessages();
-        waitForIntegerConsumerResult(1);
-        assertEquals(SatelliteManager.SATELLITE_RESULT_SUCCESS,
-                (int) mIntegerConsumerResult.get(0));
-        mIntegerConsumerResult.clear();
-        verify(mMockSatelliteModemInterface, never()).sendSatelliteDatagram(
-                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+            // Test when overlay config config_send_satellite_datagram_to_modem_in_demo_mode is true
+            mDatagramDispatcherUT.setShouldSendDatagramToModemInDemoMode(null);
+            mContextFixture.putBooleanResource(mConfigSendSatelliteDatagramToModemInDemoMode, true);
+            mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, datagramType, mDatagram,
+                    true, mIntegerConsumer);
+            processAllMessages();
+            moveTimeForward(TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE);
+            processAllMessages();
+            waitForIntegerConsumerResult(1);
+            assertEquals(SATELLITE_RESULT_SUCCESS, (int) mIntegerConsumerResult.get(0));
+            mIntegerConsumerResult.clear();
+            verify(mMockSatelliteModemInterface, times(1)).sendSatelliteDatagram(
+                    any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
 
-        // Send datagram one more time
-        reset(mMockSatelliteModemInterface);
-        mTestDemoModeDatagramDispatcher.sendSatelliteDatagram(SUB_ID, DATAGRAM_TYPE1, mDatagram,
-                true, mIntegerConsumer);
-        processAllMessages();
-        waitForIntegerConsumerResult(1);
-        assertEquals(SatelliteManager.SATELLITE_RESULT_SUCCESS,
-                (int) mIntegerConsumerResult.get(0));
-        mIntegerConsumerResult.clear();
-        verify(mMockSatelliteModemInterface, never()).sendSatelliteDatagram(
-                any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+            moveTimeForward(TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE);
+            processAllMessages();
+            verify(mMockDatagramController).pushDemoModeDatagram(
+                    anyInt(), any(SatelliteDatagram.class));
+            verify(mMockDatagramController).pollPendingSatelliteDatagrams(anyInt(), any());
+            verify(mMockSessionMetricsStats, times(1))
+                    .addCountOfSuccessfulOutgoingDatagram(anyInt());
 
-        mTestDemoModeDatagramDispatcher.setDemoMode(false);
-        mTestDemoModeDatagramDispatcher.setDeviceAlignedWithSatellite(false);
-        mTestDemoModeDatagramDispatcher.setShouldSendDatagramToModemInDemoMode(null);
+            // Test when overlay config config_send_satellite_datagram_to_modem_in_demo_mode is
+            // false
+            reset(mMockSatelliteModemInterface);
+            mDatagramDispatcherUT.setShouldSendDatagramToModemInDemoMode(null);
+            mContextFixture.putBooleanResource(mConfigSendSatelliteDatagramToModemInDemoMode,
+                    false);
+            mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, datagramType, mDatagram,
+                    true, mIntegerConsumer);
+            processAllMessages();
+            moveTimeForward(TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE);
+            processAllMessages();
+
+            waitForIntegerConsumerResult(1);
+            assertEquals(SATELLITE_RESULT_SUCCESS, (int) mIntegerConsumerResult.get(0));
+            mIntegerConsumerResult.clear();
+            verify(mMockSatelliteModemInterface, never()).sendSatelliteDatagram(
+                    any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+
+            moveTimeForward(TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE);
+            processAllMessages();
+            verify(mMockDatagramController, times(2)).pushDemoModeDatagram(
+                    anyInt(), any(SatelliteDatagram.class));
+            verify(mMockDatagramController, times(2)).pollPendingSatelliteDatagrams(anyInt(),
+                    any());
+
+            // Send datagram one more time
+            reset(mMockSatelliteModemInterface);
+            mDatagramDispatcherUT.sendSatelliteDatagram(SUB_ID, datagramType, mDatagram,
+                    true, mIntegerConsumer);
+            processAllMessages();
+            moveTimeForward(TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE);
+            processAllMessages();
+
+            waitForIntegerConsumerResult(1);
+            assertEquals(SATELLITE_RESULT_SUCCESS, (int) mIntegerConsumerResult.get(0));
+            mIntegerConsumerResult.clear();
+            verify(mMockSatelliteModemInterface, never()).sendSatelliteDatagram(
+                    any(SatelliteDatagram.class), anyBoolean(), anyBoolean(), any(Message.class));
+
+            moveTimeForward(TIMEOUT_DATAGRAM_DELAY_IN_DEMO_MODE);
+            processAllMessages();
+            verify(mMockDatagramController, times(3)).pushDemoModeDatagram(
+                    anyInt(), any(SatelliteDatagram.class));
+            verify(mMockDatagramController, times(3)).pollPendingSatelliteDatagrams(anyInt(),
+                    any());
+        }
+
+        mDatagramDispatcherUT.setDemoMode(false);
+        mDatagramDispatcherUT.setDeviceAlignedWithSatellite(false);
+        mDatagramDispatcherUT.setShouldSendDatagramToModemInDemoMode(null);
     }
 
     private boolean waitForIntegerConsumerResult(int expectedNumberOfEvents) {
@@ -640,8 +785,9 @@
         private long mLong = SATELLITE_ALIGN_TIMEOUT;
 
         TestDatagramDispatcher(@NonNull Context context, @NonNull Looper looper,
+                @NonNull FeatureFlags featureFlags,
                 @NonNull DatagramController datagramController) {
-            super(context, looper, datagramController);
+            super(context, looper, featureFlags, datagramController);
         }
 
         @Override
@@ -650,7 +796,7 @@
         }
 
         @Override
-        protected  void setDeviceAlignedWithSatellite(boolean isAligned) {
+        public void setDeviceAlignedWithSatellite(boolean isAligned) {
             super.setDeviceAlignedWithSatellite(isAligned);
         }
 
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 3a42881..947661b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DatagramReceiverTest.java
@@ -25,6 +25,7 @@
 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.eq;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.clearInvocations;
@@ -57,7 +58,9 @@
 
 import com.android.internal.telephony.IVoidConsumer;
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.flags.FeatureFlags;
 import com.android.internal.telephony.satellite.metrics.ControllerMetricsStats;
+import com.android.internal.telephony.satellite.metrics.SessionMetricsStats;
 
 import org.junit.After;
 import org.junit.Before;
@@ -90,6 +93,7 @@
     @Mock private SatelliteModemInterface mMockSatelliteModemInterface;
     @Mock private ControllerMetricsStats mMockControllerMetricsStats;
     @Mock private SatelliteSessionController mMockSatelliteSessionController;
+    @Mock private SessionMetricsStats mMockSessionMetricsStats;
 
     /** Variables required to receive datagrams in the unit tests. */
     LinkedBlockingQueue<Integer> mResultListener;
@@ -120,10 +124,14 @@
                 mMockControllerMetricsStats);
         replaceInstance(SatelliteSessionController.class, "sInstance", null,
                 mMockSatelliteSessionController);
+        replaceInstance(SessionMetricsStats.class, "sInstance", null,
+                mMockSessionMetricsStats);
 
-        mDatagramReceiverUT = DatagramReceiver.make(mContext, Looper.myLooper(),
+        when(mFeatureFlags.satellitePersistentLogging()).thenReturn(true);
+        mDatagramReceiverUT = DatagramReceiver.make(mContext, Looper.myLooper(), mFeatureFlags,
                 mMockDatagramController);
         mTestDemoModeDatagramReceiver = new TestDatagramReceiver(mContext, Looper.myLooper(),
+                mFeatureFlags,
                 mMockDatagramController);
         mSatelliteDatagramListenerHandler = new DatagramReceiver.SatelliteDatagramListenerHandler(
                 Looper.myLooper(), SUB_ID);
@@ -134,7 +142,8 @@
 
         when(mMockDatagramController.isSendingInIdleState()).thenReturn(true);
         when(mMockDatagramController.isPollingInIdleState()).thenReturn(true);
-        when(mMockDatagramController.needsWaitingForSatelliteConnected()).thenReturn(false);
+        when(mMockDatagramController.needsWaitingForSatelliteConnected(
+                eq(SatelliteManager.DATAGRAM_TYPE_UNKNOWN))).thenReturn(false);
         processAllMessages();
     }
 
@@ -163,22 +172,25 @@
                     .sendToTarget();
             return null;
         }).when(mMockSatelliteModemInterface).pollPendingSatelliteDatagrams(any(Message.class));
-        doReturn(true).when(mMockDatagramController).needsWaitingForSatelliteConnected();
-        when(mMockDatagramController.getDatagramWaitTimeForConnectedState())
+        doReturn(true).when(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(SatelliteManager.DATAGRAM_TYPE_UNKNOWN));
+        when(mMockDatagramController.getDatagramWaitTimeForConnectedState(anyBoolean()))
                 .thenReturn(TEST_DATAGRAM_WAIT_FOR_CONNECTED_STATE_TIMEOUT_MILLIS);
         mResultListener.clear();
 
         mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
         processAllMessages();
-        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
+        mInOrder.verify(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(SatelliteManager.DATAGRAM_TYPE_UNKNOWN));
         mInOrder.verify(mMockDatagramController).updateReceiveStatus(eq(SUB_ID),
                 eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT), eq(0),
                 eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
-        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState();
+        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState(eq(false));
         verifyZeroInteractions(mMockSatelliteModemInterface);
         assertTrue(mDatagramReceiverUT.isDatagramWaitForConnectedStateTimerStarted());
 
-        doReturn(false).when(mMockDatagramController).needsWaitingForSatelliteConnected();
+        doReturn(false).when(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(SatelliteManager.DATAGRAM_TYPE_UNKNOWN));
         mDatagramReceiverUT.onSatelliteModemStateChanged(
                 SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
         processAllMessages();
@@ -194,12 +206,15 @@
         assertFalse(mDatagramReceiverUT.isDatagramWaitForConnectedStateTimerStarted());
 
         clearInvocations(mMockSatelliteModemInterface);
+        clearInvocations(mMockSessionMetricsStats);
         mResultListener.clear();
-        doReturn(true).when(mMockDatagramController).needsWaitingForSatelliteConnected();
+        doReturn(true).when(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(SatelliteManager.DATAGRAM_TYPE_UNKNOWN));
         mDatagramReceiverUT.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
         processAllMessages();
-        mInOrder.verify(mMockDatagramController).needsWaitingForSatelliteConnected();
-        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState();
+        mInOrder.verify(mMockDatagramController)
+                .needsWaitingForSatelliteConnected(eq(SatelliteManager.DATAGRAM_TYPE_UNKNOWN));
+        mInOrder.verify(mMockDatagramController).getDatagramWaitTimeForConnectedState(eq(false));
         verifyZeroInteractions(mMockSatelliteModemInterface);
         assertTrue(mDatagramReceiverUT.isDatagramWaitForConnectedStateTimerStarted());
 
@@ -215,6 +230,7 @@
         assertEquals(1, mResultListener.size());
         assertThat(mResultListener.peek()).isEqualTo(
                 SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+        verify(mMockSessionMetricsStats, times(1)).addCountOfFailedIncomingDatagram();
         assertFalse(mDatagramReceiverUT.isDatagramWaitForConnectedStateTimerStarted());
 
         mResultListener.clear();
@@ -255,6 +271,8 @@
 
         assertThat(mResultListener.peek()).isEqualTo(
                 SatelliteManager.SATELLITE_RESULT_SERVICE_ERROR);
+
+        verify(mMockSessionMetricsStats, times(1)).addCountOfFailedIncomingDatagram();
     }
 
     @Test
@@ -273,6 +291,8 @@
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
                         eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+
+        verify(mMockSessionMetricsStats, never()).addCountOfFailedIncomingDatagram();
     }
 
     @Test
@@ -295,6 +315,7 @@
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE),
                         eq(0), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         assertTrue(testSatelliteDatagramCallback.waitForOnSatelliteDatagramReceived());
+        verify(mMockSessionMetricsStats, times(1)).addCountOfSuccessfulIncomingDatagram();
 
         assertTrue(testSatelliteDatagramCallback.sendInternalAck());
         try {
@@ -316,6 +337,7 @@
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS),
                         eq(10), eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
+        verify(mMockSessionMetricsStats, times(1)).addCountOfSuccessfulIncomingDatagram();
     }
 
     @Test
@@ -323,10 +345,11 @@
         // Checks invalid case only as SatelliteController does not exist in unit test
         mTestDemoModeDatagramReceiver.setDemoMode(true);
         mTestDemoModeDatagramReceiver.setDeviceAlignedWithSatellite(true);
-        when(mMockDatagramController.getDemoModeDatagram()).thenReturn(mDatagram);
+        when(mMockDatagramController.popDemoModeDatagram()).thenReturn(mDatagram);
+
         mTestDemoModeDatagramReceiver.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
         processAllMessages();
-        verify(mMockDatagramController, times(1)).getDemoModeDatagram();
+        verify(mMockDatagramController, times(1)).popDemoModeDatagram();
         verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING),
@@ -353,10 +376,11 @@
         mTestDemoModeDatagramReceiver.setDemoMode(true);
         mTestDemoModeDatagramReceiver.setDuration(TEST_EXPIRE_TIMER_SATELLITE_ALIGN);
         mTestDemoModeDatagramReceiver.setDeviceAlignedWithSatellite(false);
-        when(mMockDatagramController.getDemoModeDatagram()).thenReturn(mDatagram);
+        when(mMockDatagramController.waitForAligningToSatellite(false)).thenReturn(true);
+        when(mMockDatagramController.popDemoModeDatagram()).thenReturn(mDatagram);
         mTestDemoModeDatagramReceiver.pollPendingSatelliteDatagrams(SUB_ID, mResultListener::offer);
         processAllMessages();
-        verify(mMockDatagramController, never()).getDemoModeDatagram();
+        verify(mMockDatagramController, never()).popDemoModeDatagram();
         verify(mMockDatagramController)
                 .updateReceiveStatus(eq(SUB_ID),
                         eq(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVING),
@@ -375,6 +399,7 @@
                         eq(SatelliteManager.SATELLITE_RESULT_SUCCESS));
         assertThat(mResultListener.peek())
                 .isEqualTo(SatelliteManager.SATELLITE_RESULT_NOT_REACHABLE);
+        verify(mMockSessionMetricsStats, times(1)).addCountOfFailedIncomingDatagram();
 
         mTestDemoModeDatagramReceiver.setDemoMode(false);
         mTestDemoModeDatagramReceiver.setDeviceAlignedWithSatellite(false);
@@ -468,8 +493,9 @@
         private long mLong =  SATELLITE_ALIGN_TIMEOUT;
 
         TestDatagramReceiver(@NonNull Context context, @NonNull Looper looper,
+                @NonNull FeatureFlags featureFlags,
                 @NonNull DatagramController datagramController) {
-            super(context, looper, datagramController);
+            super(context, looper, featureFlags, datagramController);
         }
 
         @Override
@@ -478,7 +504,7 @@
         }
 
         @Override
-        protected void setDeviceAlignedWithSatellite(boolean isAligned) {
+        public void setDeviceAlignedWithSatellite(boolean isAligned) {
             super.setDeviceAlignedWithSatellite(isAligned);
         }
 
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/DemoSimulatorTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/DemoSimulatorTest.java
new file mode 100644
index 0000000..319e39f
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/DemoSimulatorTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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.satellite;
+
+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.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.Looper;
+import android.telephony.satellite.stub.ISatelliteListener;
+import android.telephony.satellite.stub.NtnSignalStrength;
+import android.telephony.satellite.stub.SatelliteModemState;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.telephony.TelephonyTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for DemoSimulator
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class DemoSimulatorTest extends TelephonyTest {
+    private static final String TAG = "DemoSimulatorTest";
+    private static final long TEST_DEVICE_POINTING_ALIGNED_DURATION_MILLIS = 200L;
+    private static final long TEST_DEVICE_POINTING_NOT_ALIGNED_DURATION_MILLIS = 300L;
+    private static final String STATE_POWER_OFF = "PowerOffState";
+    private static final String STATE_NOT_CONNECTED = "NotConnectedState";
+    private static final String STATE_CONNECTED = "ConnectedState";
+
+    private TestDemoSimulator mTestDemoSimulator;
+    @Mock private ISatelliteListener mISatelliteListener;
+
+    @Mock private SatelliteController mMockSatelliteController;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp(getClass().getSimpleName());
+        MockitoAnnotations.initMocks(this);
+
+        when(mMockSatelliteController.isDemoModeEnabled()).thenReturn(true);
+        when(mMockSatelliteController.getDemoPointingAlignedDurationMillis()).thenReturn(
+                TEST_DEVICE_POINTING_ALIGNED_DURATION_MILLIS);
+        when(mMockSatelliteController.getDemoPointingNotAlignedDurationMillis()).thenReturn(
+                TEST_DEVICE_POINTING_NOT_ALIGNED_DURATION_MILLIS);
+
+        mTestDemoSimulator = new TestDemoSimulator(mContext, Looper.myLooper(),
+                mMockSatelliteController);
+        mTestDemoSimulator.setSatelliteListener(mISatelliteListener);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testInitialState() {
+        assertNotNull(mTestDemoSimulator);
+        processAllMessages();
+        assertEquals(STATE_POWER_OFF, mTestDemoSimulator.getCurrentStateName());
+    }
+
+    @Test
+    public void testStateTransition() {
+        // State transitions: POWER_OFF -> NOT_CONNECTED -> CONNECTED
+        moveToConnectedState();
+
+        // Device is not aligned with satellite. EVENT_DEVICE_NOT_ALIGNED timer should start
+        mTestDemoSimulator.setDeviceAlignedWithSatellite(false);
+        processAllMessages();
+        assertTrue(mTestDemoSimulator.isDeviceNotAlignedTimerStarted());
+
+        // After timeout, DemoSimulator should move to NOT_CONNECTED state.
+        moveTimeForward(TEST_DEVICE_POINTING_NOT_ALIGNED_DURATION_MILLIS);
+        processAllMessages();
+        assertEquals(STATE_NOT_CONNECTED, mTestDemoSimulator.getCurrentStateName());
+
+        // Satellite mode is OFF. DemoSimulator should move to POWER_OFF state.
+        mTestDemoSimulator.onSatelliteModeOff();
+        processAllMessages();
+        assertEquals(STATE_POWER_OFF, mTestDemoSimulator.getCurrentStateName());
+    }
+
+    @Test
+    public void testNotConnectedState_enter() throws Exception {
+        clearInvocations(mISatelliteListener);
+
+        // State transitions: POWER_OFF -> NOT_CONNECTED
+        moveToNotConnectedState();
+
+        verify(mISatelliteListener).onSatelliteModemStateChanged(
+                SatelliteModemState.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        ArgumentCaptor<NtnSignalStrength> ntnSignalStrength = ArgumentCaptor.forClass(
+                NtnSignalStrength.class);
+        verify(mISatelliteListener).onNtnSignalStrengthChanged(ntnSignalStrength.capture());
+        assertEquals(0, ntnSignalStrength.getValue().signalStrengthLevel);
+    }
+
+    @Test
+    public void testNotConnectedState() {
+        // State transitions: POWER_OFF -> NOT_CONNECTED
+        moveToNotConnectedState();
+
+        // Device is aligned with satellite. EVENT_DEVICE_ALIGNED timer should start.
+        mTestDemoSimulator.setDeviceAlignedWithSatellite(true);
+        processAllMessages();
+        assertTrue(mTestDemoSimulator.isDeviceAlignedTimerStarted());
+
+        // Device is not aligned with satellite. EVENT_DEVICE_ALIGNED messages should be removed.
+        mTestDemoSimulator.setDeviceAlignedWithSatellite(false);
+        processAllMessages();
+        assertFalse(mTestDemoSimulator.isDeviceAlignedTimerStarted());
+        assertEquals(STATE_NOT_CONNECTED, mTestDemoSimulator.getCurrentStateName());
+
+        // Satellite mode is OFF. DemoSimulator should move to POWER_OFF state.
+        mTestDemoSimulator.onSatelliteModeOff();
+        processAllMessages();
+        assertEquals(STATE_POWER_OFF, mTestDemoSimulator.getCurrentStateName());
+    }
+
+    @Test
+    public void testConnectedState_enter() throws Exception {
+        clearInvocations(mISatelliteListener);
+
+        // State transitions: POWER_OFF -> NOT_CONNECTED -> CONNECTED
+        moveToConnectedState();
+
+        verify(mISatelliteListener).onSatelliteModemStateChanged(
+                SatelliteModemState.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        verify(mISatelliteListener).onSatelliteModemStateChanged(
+                SatelliteModemState.SATELLITE_MODEM_STATE_CONNECTED);
+        ArgumentCaptor<NtnSignalStrength> ntnSignalStrength = ArgumentCaptor.forClass(
+                NtnSignalStrength.class);
+        verify(mISatelliteListener, times(2))
+                .onNtnSignalStrengthChanged(ntnSignalStrength.capture());
+        NtnSignalStrength ntnSignalStrengthOnConnected = ntnSignalStrength.getAllValues().get(1);
+        assertEquals(2, ntnSignalStrengthOnConnected.signalStrengthLevel);
+    }
+
+    @Test
+    public void testConnectedState() {
+        // State transitions: POWER_OFF -> NOT_CONNECTED -> CONNECTED
+        moveToConnectedState();
+
+        // Device is not aligned with satellite. EVENT_DEVICE_NOT_ALIGNED timer should start
+        mTestDemoSimulator.setDeviceAlignedWithSatellite(false);
+        processAllMessages();
+        assertTrue(mTestDemoSimulator.isDeviceNotAlignedTimerStarted());
+
+        // Device is aligned with satellite before timeout.
+        // EVENT_DEVICE_NOT_ALIGNED messages should be removed.
+        mTestDemoSimulator.setDeviceAlignedWithSatellite(true);
+        processAllMessages();
+        assertFalse(mTestDemoSimulator.isDeviceNotAlignedTimerStarted());
+        assertEquals(STATE_CONNECTED, mTestDemoSimulator.getCurrentStateName());
+
+        // Satellite mode is off. DemoSimulator should move to POWER_OFF state
+        mTestDemoSimulator.onSatelliteModeOff();
+        processAllMessages();
+        assertEquals(STATE_POWER_OFF, mTestDemoSimulator.getCurrentStateName());
+    }
+
+    private void moveToNotConnectedState() {
+        // DemoSimulator will initially be in POWER_OFF state.
+        assertNotNull(mTestDemoSimulator);
+        processAllMessages();
+        assertEquals(STATE_POWER_OFF, mTestDemoSimulator.getCurrentStateName());
+
+        // Satellite mode is ON. DemoSimulator should move to NOT_CONNECTED state.
+        mTestDemoSimulator.onSatelliteModeOn();
+        processAllMessages();
+        assertEquals(STATE_NOT_CONNECTED, mTestDemoSimulator.getCurrentStateName());
+    }
+
+    private void moveToConnectedState() {
+        // DemoSimulator will initially be in POWER_OFF state.
+        assertNotNull(mTestDemoSimulator);
+        processAllMessages();
+        assertEquals(STATE_POWER_OFF, mTestDemoSimulator.getCurrentStateName());
+
+        // Satellite mode is ON. DemoSimulator should move to NOT_CONNECTED state.
+        mTestDemoSimulator.onSatelliteModeOn();
+        processAllMessages();
+        assertEquals(STATE_NOT_CONNECTED, mTestDemoSimulator.getCurrentStateName());
+
+        // Device is aligned with satellite. EVENT_DEVICE_ALIGNED timer should start.
+        mTestDemoSimulator.setDeviceAlignedWithSatellite(true);
+        processAllMessages();
+        assertTrue(mTestDemoSimulator.isDeviceAlignedTimerStarted());
+
+        // After timeout, DemoSimulator should move to CONNECTED state.
+        moveTimeForward(TEST_DEVICE_POINTING_ALIGNED_DURATION_MILLIS);
+        processAllMessages();
+        assertEquals(STATE_CONNECTED, mTestDemoSimulator.getCurrentStateName());
+    }
+
+    private static class TestDemoSimulator extends DemoSimulator {
+
+        TestDemoSimulator(@NonNull Context context, @NonNull Looper looper,
+                @NonNull SatelliteController satelliteController) {
+            super(context, looper, satelliteController);
+        }
+
+        String getCurrentStateName() {
+            return getCurrentState().getName();
+        }
+
+        boolean isDeviceAlignedTimerStarted() {
+            return hasMessages(EVENT_DEVICE_ALIGNED);
+        }
+
+        boolean isDeviceNotAlignedTimerStarted() {
+            return hasMessages(EVENT_DEVICE_NOT_ALIGNED);
+        }
+    }
+}
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 f8827be..873078e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/NtnCapabilityResolverTest.java
@@ -30,6 +30,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.NonNull;
 import android.telephony.CellIdentity;
@@ -100,6 +102,7 @@
                 .mapToInt(Integer::intValue)
                 .toArray()));
         NtnCapabilityResolver.resolveNtnCapability(satelliteNri, SUB_ID);
+        verify(mMockSatelliteController).getSatellitePlmnsForCarrier(anyInt());
         assertNotEquals(satelliteNri, originalNri);
         assertTrue(satelliteNri.isNonTerrestrialNetwork());
         assertTrue(Arrays.equals(mSatelliteSupportedServices,
@@ -118,12 +121,32 @@
                         .mapToInt(Integer::intValue)
                         .toArray()));
         NtnCapabilityResolver.resolveNtnCapability(cellularNri, SUB_ID);
+        verify(mMockSatelliteController, times(2)).getSatellitePlmnsForCarrier(anyInt());
         assertEquals(cellularNri, originalNri);
         assertFalse(cellularNri.isNonTerrestrialNetwork());
         assertFalse(Arrays.equals(mSatelliteSupportedServices,
                 cellularNri.getAvailableServices().stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
+
+        // Test resolving an empty-PLMN NetworkRegistrationInfo.
+        NetworkRegistrationInfo emptyPlmnNri = createNetworkRegistrationInfo("");
+        originalNri = new NetworkRegistrationInfo(emptyPlmnNri);
+
+        assertEquals(emptyPlmnNri, originalNri);
+        assertFalse(emptyPlmnNri.isNonTerrestrialNetwork());
+        assertFalse(Arrays.equals(mSatelliteSupportedServices,
+                emptyPlmnNri.getAvailableServices().stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+        NtnCapabilityResolver.resolveNtnCapability(emptyPlmnNri, SUB_ID);
+        verify(mMockSatelliteController, times(2)).getSatellitePlmnsForCarrier(anyInt());
+        assertEquals(emptyPlmnNri, originalNri);
+        assertFalse(emptyPlmnNri.isNonTerrestrialNetwork());
+        assertFalse(Arrays.equals(mSatelliteSupportedServices,
+                emptyPlmnNri.getAvailableServices().stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
     }
 
     private NetworkRegistrationInfo createNetworkRegistrationInfo(@NonNull String registeredPlmn) {
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 3917a32..36d32fe 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/PointingAppControllerTest.java
@@ -76,6 +76,8 @@
     private static final String KEY_POINTING_UI_PACKAGE_NAME = "default_pointing_ui_package";
     private static final String KEY_POINTING_UI_CLASS_NAME = "default_pointing_ui_class";
     private static final String KEY_NEED_FULL_SCREEN = "needFullScreen";
+    private static final String KEY_IS_DEMO_MODE = "isDemoMode";
+    private static final String KEY_IS_EMERGENCY = "isEmergency";
 
     private PointingAppController mPointingAppController;
     InOrder mInOrder;
@@ -98,12 +100,13 @@
         super.setUp(getClass().getSimpleName());
         MockitoAnnotations.initMocks(this);
         logd(TAG + " Setup!");
+        when(mFeatureFlags.satellitePersistentLogging()).thenReturn(true);
         mInOrderForPointingUi = inOrder(mContext);
         replaceInstance(SatelliteModemInterface.class, "sInstance", null,
                 mMockSatelliteModemInterface);
         replaceInstance(SatelliteController.class, "sInstance", null,
                 mMockSatelliteController);
-        mPointingAppController = new PointingAppController(mContext);
+        mPointingAppController = new PointingAppController(mContext, mFeatureFlags);
         mContextFixture.putResource(R.string.config_pointing_ui_package,
                 KEY_POINTING_UI_PACKAGE_NAME);
         mContextFixture.putResource(R.string.config_pointing_ui_class,
@@ -150,6 +153,7 @@
     }
     private class TestSatelliteTransmissionUpdateCallback
                                 extends ISatelliteTransmissionUpdateCallback.Stub {
+        int mDatagramType;
         int mState;
         int mSendPendingCount;
         int mReceivePendingCount;
@@ -163,8 +167,9 @@
         }
 
         @Override
-        public void onSendDatagramStateChanged(int state, int sendPendingCount,
+        public void onSendDatagramStateChanged(int datagramType, int state, int sendPendingCount,
                                     int errorCode) {
+            mDatagramType = datagramType;
             mState = state;
             mSendPendingCount = sendPendingCount;
             mErrorCode = errorCode;
@@ -191,6 +196,10 @@
             }
         }
 
+        public int getDatagramType() {
+            return mDatagramType;
+        }
+
         public int getState() {
             return mState;
         }
@@ -208,7 +217,7 @@
         }
     }
 
-    private boolean waitForReceiveDatagramStateChangedRessult(
+    private boolean waitForReceiveDatagramStateChangedResult(
             int expectedNumberOfEvents) {
         for (int i = 0; i < expectedNumberOfEvents; i++) {
             try {
@@ -218,7 +227,7 @@
                     return false;
                 }
             } catch (Exception ex) {
-                loge("waitForReceiveDatagramStateChangedRessult: Got exception=" + ex);
+                loge("waitForReceiveDatagramStateChangedResult: Got exception=" + ex);
                 return false;
             }
         }
@@ -306,7 +315,7 @@
     @Test
     public void testStartPointingUI() throws Exception {
         ArgumentCaptor<Intent> startedIntentCaptor = ArgumentCaptor.forClass(Intent.class);
-        mPointingAppController.startPointingUI(true);
+        mPointingAppController.startPointingUI(true, true, true);
         verify(mContext).startActivity(startedIntentCaptor.capture());
         Intent intent = startedIntentCaptor.getValue();
         assertEquals(KEY_POINTING_UI_PACKAGE_NAME, intent.getComponent().getPackageName());
@@ -314,19 +323,24 @@
         Bundle b = intent.getExtras();
         assertTrue(b.containsKey(KEY_NEED_FULL_SCREEN));
         assertTrue(b.getBoolean(KEY_NEED_FULL_SCREEN));
+        assertTrue(b.containsKey(KEY_IS_DEMO_MODE));
+        assertTrue(b.getBoolean(KEY_IS_DEMO_MODE));
+        assertTrue(b.containsKey(KEY_IS_EMERGENCY));
+        assertTrue(b.getBoolean(KEY_IS_EMERGENCY));
     }
 
     @Test
     public void testRestartPointingUi() throws Exception {
-        mPointingAppController.startPointingUI(true);
+        mPointingAppController.startPointingUI(true, false, true);
         mInOrderForPointingUi.verify(mContext).startActivity(any(Intent.class));
-        testRestartPointingUi(true);
-        mPointingAppController.startPointingUI(false);
+        testRestartPointingUi(true, false, true);
+        mPointingAppController.startPointingUI(false, true, false);
         mInOrderForPointingUi.verify(mContext).startActivity(any(Intent.class));
-        testRestartPointingUi(false);
+        testRestartPointingUi(false, true, false);
     }
 
-    private void testRestartPointingUi(boolean expectedFullScreen) {
+    private void testRestartPointingUi(boolean expectedFullScreen, boolean expectedDemoMode,
+            boolean expectedEmergency) {
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         doReturn(new String[]{KEY_POINTING_UI_PACKAGE_NAME}).when(mPackageManager)
             .getPackagesForUid(anyInt());
@@ -340,6 +354,12 @@
         assertTrue(b.containsKey(KEY_NEED_FULL_SCREEN));
         // Checking if last value of KEY_NEED_FULL_SCREEN is taken or not
         assertEquals(expectedFullScreen, b.getBoolean(KEY_NEED_FULL_SCREEN));
+        assertTrue(b.containsKey(KEY_IS_DEMO_MODE));
+        // Checking if last value of KEY_IS_DEMO_MODE is taken or not
+        assertEquals(expectedDemoMode, b.getBoolean(KEY_IS_DEMO_MODE));
+        assertTrue(b.containsKey(KEY_IS_EMERGENCY));
+        // Checking if last value of KEY_IS_EMERGENCY is taken or not
+        assertEquals(expectedEmergency, b.getBoolean(KEY_IS_EMERGENCY));
     }
 
     @Test
@@ -347,9 +367,12 @@
         mPointingAppController.registerForSatelliteTransmissionUpdates(SUB_ID,
                 mSatelliteTransmissionUpdateCallback);
         mPointingAppController.updateSendDatagramTransferState(SUB_ID,
+                SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE,
                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS, 1,
                 SatelliteManager.SATELLITE_RESULT_SUCCESS);
         assertTrue(waitForSendDatagramStateChangedRessult(1));
+        assertEquals(SatelliteManager.DATAGRAM_TYPE_KEEP_ALIVE,
+                mSatelliteTransmissionUpdateCallback.getDatagramType());
         assertEquals(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS,
                 mSatelliteTransmissionUpdateCallback.getState());
         assertEquals(1, mSatelliteTransmissionUpdateCallback.getSendPendingCount());
@@ -368,7 +391,7 @@
         mPointingAppController.updateReceiveDatagramTransferState(SUB_ID,
                 SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS, 2,
                 SatelliteManager.SATELLITE_RESULT_SUCCESS);
-        assertTrue(waitForReceiveDatagramStateChangedRessult(1));
+        assertTrue(waitForReceiveDatagramStateChangedResult(1));
         assertEquals(SatelliteManager.SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_SUCCESS,
                 mSatelliteTransmissionUpdateCallback.getState());
         assertEquals(2, mSatelliteTransmissionUpdateCallback.getReceivePendingCount());
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteConfigParserTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteConfigParserTest.java
index e4f0255..7b4cf8d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteConfigParserTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteConfigParserTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.satellite;
 
 import static junit.framework.Assert.assertNotNull;
+import static junit.framework.TestCase.assertFalse;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -24,6 +25,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 
 import android.testing.AndroidTestingRunner;
@@ -34,11 +36,9 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.nio.file.Path;
-import java.nio.file.Paths;
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Base64;
 import java.util.Collections;
@@ -182,30 +182,27 @@
 
     @Test
     public void testGetSatelliteS2CellFile() {
-        final String filePath = "/data/user_de/0/com.android.phone/app_satellite/s2_cell_file";
-        Path targetSatS2FilePath = Paths.get(filePath);
-
-        SatelliteConfigParser mockedSatelliteConfigParserNull = spy(
+        SatelliteConfigParser spySatelliteConfigParserNull = spy(
                 new SatelliteConfigParser((byte[]) null));
-        assertNotNull(mockedSatelliteConfigParserNull);
-        assertNull(mockedSatelliteConfigParserNull.getConfig());
+        assertNotNull(spySatelliteConfigParserNull);
+        assertNull(spySatelliteConfigParserNull.getConfig());
 
-        SatelliteConfigParser mockedSatelliteConfigParserPlaceholder = spy(
+        SatelliteConfigParser spySatelliteConfigParserPlaceholder = spy(
                 new SatelliteConfigParser("test".getBytes()));
-        assertNotNull(mockedSatelliteConfigParserPlaceholder);
-        assertNull(mockedSatelliteConfigParserPlaceholder.getConfig());
+        assertNotNull(spySatelliteConfigParserPlaceholder);
+        assertNull(spySatelliteConfigParserPlaceholder.getConfig());
 
-        SatelliteConfigParser mockedSatelliteConfigParser =
+        SatelliteConfigParser spySatelliteConfigParser =
                 spy(new SatelliteConfigParser(mBytesProtoBuffer));
-        SatelliteConfig mockedSatelliteConfig = Mockito.mock(SatelliteConfig.class);
-        doReturn(targetSatS2FilePath).when(mockedSatelliteConfig).getSatelliteS2CellFile(any());
-        doReturn(mockedSatelliteConfig).when(mockedSatelliteConfigParser).getConfig();
-//        assertNotNull(mockedSatelliteConfigParser.getConfig());
-//        doReturn(false).when(mockedSatelliteConfigParser).getConfig().isFileExist(any());
-//        doReturn(targetSatS2FilePath).when(mockedSatelliteConfigParser).getConfig()
-//                .copySatS2FileToPhoneDirectory(any(), any());
-        assertEquals(targetSatS2FilePath,
-                mockedSatelliteConfigParser.getConfig().getSatelliteS2CellFile(mContext));
+        assertNotNull(spySatelliteConfigParser.getConfig());
+        assertFalse(spySatelliteConfigParser.getConfig().isFileExist(null));
+
+        SatelliteConfig mockedSatelliteConfig = mock(SatelliteConfig.class);
+        File mMockSatS2File = mock(File.class);
+        doReturn(mMockSatS2File).when(mockedSatelliteConfig).getSatelliteS2CellFile(any());
+        doReturn(mockedSatelliteConfig).when(spySatelliteConfigParser).getConfig();
+        assertEquals(mMockSatS2File,
+                spySatelliteConfigParser.getConfig().getSatelliteS2CellFile(mContext));
     }
 
     @Test
diff --git a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
index f1a8de9..da40c32 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteControllerTest.java
@@ -16,8 +16,10 @@
 
 package com.android.internal.telephony.satellite;
 
+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.NetworkRegistrationInfo.SERVICE_TYPE_DATA;
 import static android.telephony.SubscriptionManager.SATELLITE_ENTITLEMENT_STATUS;
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_GOOD;
 import static android.telephony.satellite.NtnSignalStrength.NTN_SIGNAL_STRENGTH_GREAT;
@@ -57,6 +59,7 @@
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SERVICE_PROVISION_IN_PROGRESS;
 import static android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
+import static com.android.internal.telephony.satellite.SatelliteController.DEFAULT_CARRIER_EMERGENCY_CALL_WAIT_FOR_CONNECTION_TIMEOUT_MILLIS;
 import static com.android.internal.telephony.satellite.SatelliteController.SATELLITE_MODE_ENABLED_FALSE;
 import static com.android.internal.telephony.satellite.SatelliteController.SATELLITE_MODE_ENABLED_TRUE;
 
@@ -70,6 +73,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.anyVararg;
 import static org.mockito.ArgumentMatchers.eq;
@@ -99,6 +103,8 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.telephony.CarrierConfigManager;
+import android.telephony.CellSignalStrength;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.satellite.INtnSignalStrengthCallback;
@@ -106,6 +112,7 @@
 import android.telephony.satellite.ISatelliteDatagramCallback;
 import android.telephony.satellite.ISatelliteModemStateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
+import android.telephony.satellite.ISatelliteSupportedStateCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteCapabilities;
@@ -170,6 +177,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 String SATELLITE_PLMN = "00103";
     private List<Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener>>
             mCarrierConfigChangedListenerList = new ArrayList<>();
 
@@ -193,7 +202,9 @@
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private TelephonyConfigUpdateInstallReceiver mMockTelephonyConfigUpdateInstallReceiver;
     @Mock private SatelliteConfigParser mMockConfigParser;
+    @Mock private CellSignalStrength mCellSignalStrength;
     @Mock private SatelliteConfig mMockConfig;
+    @Mock private DemoSimulator mMockDemoSimulator;
 
     private Semaphore mIIntegerConsumerSemaphore = new Semaphore(0);
     private IIntegerConsumer mIIntegerConsumer = new IIntegerConsumer.Stub() {
@@ -467,12 +478,15 @@
         replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, mPhone2});
         replaceInstance(TelephonyConfigUpdateInstallReceiver.class, "sReceiverAdaptorInstance",
                 null, mMockTelephonyConfigUpdateInstallReceiver);
+        replaceInstance(DemoSimulator.class, "sInstance", null, mMockDemoSimulator);
 
         mServiceState2 = Mockito.mock(ServiceState.class);
         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);
 
         mContextFixture.putStringArrayResource(
                 R.array.config_satellite_providers,
@@ -513,13 +527,23 @@
         doReturn(mMockSessionMetricsStats)
                 .when(mMockSessionMetricsStats).setInitializationResult(anyInt());
         doReturn(mMockSessionMetricsStats)
-                .when(mMockSessionMetricsStats).setRadioTechnology(anyInt());
+                .when(mMockSessionMetricsStats).setSatelliteTechnology(anyInt());
+        doReturn(mMockSessionMetricsStats)
+                .when(mMockSessionMetricsStats).setTerminationResult(anyInt());
+        doReturn(mMockSessionMetricsStats)
+                .when(mMockSessionMetricsStats).setInitializationProcessingTime(anyLong());
+        doReturn(mMockSessionMetricsStats)
+                .when(mMockSessionMetricsStats).setTerminationProcessingTime(anyLong());
+        doReturn(mMockSessionMetricsStats)
+                .when(mMockSessionMetricsStats).setSessionDurationSec(anyInt());
+        doReturn(mMockSessionMetricsStats)
+                .when(mMockSessionMetricsStats).setIsDemoMode(anyBoolean());
         doNothing().when(mMockSessionMetricsStats).reportSessionMetrics();
 
         doReturn(mMockProvisionMetricsStats).when(mMockProvisionMetricsStats)
                 .setResultCode(anyInt());
         doReturn(mMockProvisionMetricsStats).when(mMockProvisionMetricsStats)
-                .setIsProvisionRequest(eq(false));
+                .setIsProvisionRequest(anyBoolean());
         doNothing().when(mMockProvisionMetricsStats).reportProvisionMetrics();
         doNothing().when(mMockControllerMetricsStats).reportDeprovisionCount(anyInt());
         when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
@@ -557,58 +581,6 @@
     }
 
     @Test
-    public void testRequestIsSatelliteCommunicationAllowedForCurrentLocation() {
-        mSatelliteAllowedSemaphore.drainPermits();
-        setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
-                mSatelliteAllowedReceiver);
-        processAllMessages();
-        assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
-        assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, mQueriedSatelliteAllowedResultCode);
-
-        resetSatelliteControllerUT();
-        mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
-                mSatelliteAllowedReceiver);
-        processAllMessages();
-        assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
-        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, mQueriedSatelliteAllowedResultCode);
-
-        resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestIsSatelliteAllowedForCurrentLocation(true,
-                SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
-                mSatelliteAllowedReceiver);
-        processAllMessages();
-        assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
-        assertEquals(SATELLITE_RESULT_SUCCESS, mQueriedSatelliteAllowedResultCode);
-        assertTrue(mQueriedSatelliteAllowed);
-
-        resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpNullResponseForRequestIsSatelliteAllowedForCurrentLocation(SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
-                mSatelliteAllowedReceiver);
-        processAllMessages();
-        assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
-        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE, mQueriedSatelliteAllowedResultCode);
-
-        resetSatelliteControllerUT();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        setUpNullResponseForRequestIsSatelliteAllowedForCurrentLocation(
-                SATELLITE_RESULT_INVALID_MODEM_STATE);
-        mSatelliteControllerUT.requestIsSatelliteCommunicationAllowedForCurrentLocation(SUB_ID,
-                mSatelliteAllowedReceiver);
-        processAllMessages();
-        assertTrue(waitForRequestIsSatelliteAllowedForCurrentLocationResult(1));
-        assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, mQueriedSatelliteAllowedResultCode);
-    }
-
-    @Test
     public void testRequestTimeForNextSatelliteVisibility() {
         mSatelliteVisibilityTimeSemaphore.drainPermits();
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
@@ -737,12 +709,44 @@
     }
 
     @Test
+    public void testRadioPowerOff() {
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        NetworkRegistrationInfo satelliteNri = new NetworkRegistrationInfo.Builder()
+                .setIsNonTerrestrialNetwork(true)
+                .setAvailableServices(List.of(NetworkRegistrationInfo.SERVICE_TYPE_DATA))
+                .build();
+        mCarrierConfigBundle.putInt(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, 1 * 60);
+        mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        when(mServiceState.getNetworkRegistrationInfoList()).thenReturn(List.of(satelliteNri));
+        when(mServiceState.isUsingNonTerrestrialNetwork()).thenReturn(true);
+        sendServiceStateChangedEvent();
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone));
+        assertEquals(List.of(SERVICE_TYPE_DATA),
+                mSatelliteControllerUT.getCapabilitiesForCarrierRoamingSatelliteMode(mPhone));
+
+        when(mServiceState.isUsingNonTerrestrialNetwork()).thenReturn(false);
+        setRadioPower(false);
+        processAllMessages();
+        assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone));
+        assertEquals(new ArrayList<>(),
+                mSatelliteControllerUT.getCapabilitiesForCarrierRoamingSatelliteMode(mPhone));
+    }
+
+    @Test
     public void testRequestSatelliteEnabled() {
         mIsSatelliteEnabledSemaphore.drainPermits();
 
         // Fail to enable satellite when SatelliteController is not fully loaded yet.
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
@@ -752,7 +756,8 @@
         mIIntegerConsumerResults.clear();
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_NOT_SUPPORTED, (long) mIIntegerConsumerResults.get(0));
@@ -767,7 +772,8 @@
         verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestIsSatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
         verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
@@ -780,13 +786,17 @@
         // Successfully enable satellite
         mIIntegerConsumerResults.clear();
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
+        mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
         verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
         assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
+        assertTrue(mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled);
         assertEquals(
                 SATELLITE_MODE_ENABLED_TRUE, mSatelliteControllerUT.satelliteModeSettingValue);
         verify(mMockSatelliteSessionController, times(1)).onSatelliteEnabledStateChanged(eq(true));
@@ -797,7 +807,8 @@
 
         // Successfully disable satellite when radio is turned off.
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
-        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
+        setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
         setRadioPower(false);
         mSatelliteControllerUT.onCellularRadioPowerOffRequested();
         processAllMessages();
@@ -805,6 +816,7 @@
         processAllMessages();
         verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
         assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
+        assertTrue(mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled);
         assertEquals(
                 SATELLITE_MODE_ENABLED_FALSE, mSatelliteControllerUT.satelliteModeSettingValue);
         verify(mMockSatelliteSessionController, times(2)).onSatelliteEnabledStateChanged(eq(false));
@@ -814,8 +826,9 @@
 
         // Fail to enable satellite when radio is off.
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         // Radio is not on, can not enable satellite
@@ -829,39 +842,45 @@
         mIIntegerConsumerResults.clear();
         clearInvocations(mMockPointingAppController);
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_INVALID_MODEM_STATE);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
+        setUpResponseForRequestSatelliteEnabled(true, false, false,
+                SATELLITE_RESULT_INVALID_MODEM_STATE);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
         verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
-        verify(mMockPointingAppController, never()).startPointingUI(anyBoolean());
+        verify(mMockPointingAppController, never()).startPointingUI(anyBoolean(), anyBoolean(),
+                anyBoolean());
         assertFalse(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
+        assertFalse(mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled);
         verify(mMockControllerMetricsStats, times(1)).reportServiceEnablementFailCount();
 
         // Successfully enable satellite when radio is on.
         mIIntegerConsumerResults.clear();
         mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
         verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
         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(mMockControllerMetricsStats, times(2)).onSatelliteEnabled();
         verify(mMockControllerMetricsStats, times(2)).reportServiceEnablementSuccessCount();
-        verify(mMockSessionMetricsStats, times(7)).setInitializationResult(anyInt());
-        verify(mMockSessionMetricsStats, times(7)).setRadioTechnology(anyInt());
-        verify(mMockSessionMetricsStats, times(7)).reportSessionMetrics();
 
         // Successfully enable satellite when it is already enabled.
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -869,7 +888,8 @@
 
         // Fail to enable satellite with a different demo mode when it is already enabled.
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, true, mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, true, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_INVALID_ARGUMENTS, (long) mIIntegerConsumerResults.get(0));
@@ -877,8 +897,9 @@
 
         // Successfully disable satellite.
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -886,7 +907,8 @@
 
         // Disable satellite when satellite is already disabled.
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -894,7 +916,8 @@
 
         // Disable satellite with a different demo mode when satellite is already disabled.
         mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, true, mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, true, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -902,11 +925,13 @@
 
         // Send a second request while the first request in progress
         mIIntegerConsumerResults.clear();
-        setUpNoResponseForRequestSatelliteEnabled(true, false);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        setUpNoResponseForRequestSatelliteEnabled(true, false, false);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_REQUEST_IN_PROGRESS, (long) mIIntegerConsumerResults.get(0));
@@ -918,14 +943,16 @@
         assertEquals(SATELLITE_RESULT_INVALID_MODEM_STATE, (long) mIIntegerConsumerResults.get(0));
 
         // Move to satellite-disabling in progress.
-        setUpNoResponseForRequestSatelliteEnabled(false, false);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        setUpNoResponseForRequestSatelliteEnabled(false, false, false);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, 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, mIIntegerConsumer);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_ERROR, (long) mIIntegerConsumerResults.get(0));
@@ -936,20 +963,30 @@
         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();
+
         /**
          * Make areAllRadiosDisabled return false and move mWaitingForRadioDisabled to true, which
          * will lead to no response for requestSatelliteEnabled.
          */
         mSatelliteControllerUT.allRadiosDisabled = false;
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
 
         resetSatelliteControllerUTEnabledState();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         // We should receive 2 callbacks for the above 2 requests.
         assertTrue(waitForIIntegerConsumerResult(2));
@@ -960,15 +997,17 @@
 
         // Repeat the same test as above but with error response from modem for the second request
         mSatelliteControllerUT.allRadiosDisabled = false;
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
 
         resetSatelliteControllerUTEnabledState();
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_RESULT_NO_RESOURCES);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_NO_RESOURCES);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         // We should receive 2 callbacks for the above 2 requests.
         assertTrue(waitForIIntegerConsumerResult(2));
@@ -1252,7 +1291,7 @@
 
         setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
         setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_RESULT_SUCCESS);
+        setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
 
         setUpResponseForRequestIsSatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.onSatelliteServiceConnected();
@@ -1278,6 +1317,7 @@
                 .registerForSatelliteModemStateChanged(callback);
 
         resetSatelliteControllerUTToSupportedAndProvisionedState();
+        mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
 
         errorCode = mSatelliteControllerUT.registerForSatelliteModemStateChanged(
                 SUB_ID, callback);
@@ -1298,7 +1338,7 @@
                 .unregisterForSatelliteModemStateChanged(callback);
 
         resetSatelliteControllerUTToSupportedAndProvisionedState();
-
+        mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
         mSatelliteControllerUT.unregisterForModemStateChanged(SUB_ID, callback);
         verify(mMockSatelliteSessionController).unregisterForSatelliteModemStateChanged(callback);
     }
@@ -1388,45 +1428,54 @@
         String mText = "This is a test datagram message from user";
         SatelliteDatagram datagram = new SatelliteDatagram(mText.getBytes());
 
-        mIIntegerConsumerResults.clear();
-        mSatelliteControllerUT.sendDatagram(SUB_ID,
-                SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, datagram, true, mIIntegerConsumer);
-        processAllMessages();
-        assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
-                (long) mIIntegerConsumerResults.get(0));
-        verify(mMockDatagramController, never()).sendSatelliteDatagram(anyInt(),
-                eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE), eq(datagram), eq(true),
-                any());
+        int[] sosDatagramTypes = {SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE,
+                SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
+                SatelliteManager.DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED};
+        for (int datagramType : sosDatagramTypes) {
+            mSatelliteControllerUT =
+                    new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
+            mIIntegerConsumerSemaphore.drainPermits();
+            mIIntegerConsumerResults.clear();
+            clearInvocations(mMockDatagramController);
+            clearInvocations(mMockPointingAppController);
 
-        mIIntegerConsumerResults.clear();
-        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
-        sendProvisionedStateChangedEvent(false, null);
-        processAllMessages();
-        verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.sendDatagram(SUB_ID,
-                SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, datagram, true, mIIntegerConsumer);
-        processAllMessages();
-        assertTrue(waitForIIntegerConsumerResult(1));
-        assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
-                (long) mIIntegerConsumerResults.get(0));
-        verify(mMockDatagramController, never()).sendSatelliteDatagram(anyInt(),
-                eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE), eq(datagram), eq(true),
-                any());
+            mSatelliteControllerUT.sendDatagram(SUB_ID, datagramType, datagram, true,
+                    mIIntegerConsumer);
+            processAllMessages();
+            assertTrue(waitForIIntegerConsumerResult(1));
+            assertEquals(SATELLITE_RESULT_INVALID_TELEPHONY_STATE,
+                    (long) mIIntegerConsumerResults.get(0));
+            verify(mMockDatagramController, never()).sendSatelliteDatagram(anyInt(),
+                    eq(datagramType), eq(datagram), eq(true), any());
 
-        mIIntegerConsumerResults.clear();
-        sendProvisionedStateChangedEvent(true, null);
-        processAllMessages();
-        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.sendDatagram(SUB_ID,
-                SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE, datagram, true, mIIntegerConsumer);
-        processAllMessages();
-        assertFalse(waitForIIntegerConsumerResult(1));
-        verify(mMockDatagramController, times(1)).sendSatelliteDatagram(anyInt(),
-                eq(SatelliteManager.DATAGRAM_TYPE_SOS_MESSAGE), eq(datagram), eq(true),
-                any());
-        verify(mMockPointingAppController, times(1)).startPointingUI(eq(true));
+            mIIntegerConsumerResults.clear();
+            setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+            verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+            sendProvisionedStateChangedEvent(false, null);
+            processAllMessages();
+            verifySatelliteProvisioned(false, SATELLITE_RESULT_SUCCESS);
+            mSatelliteControllerUT.sendDatagram(SUB_ID, datagramType, datagram, true,
+                    mIIntegerConsumer);
+            processAllMessages();
+            assertTrue(waitForIIntegerConsumerResult(1));
+            assertEquals(SATELLITE_RESULT_SERVICE_NOT_PROVISIONED,
+                    (long) mIIntegerConsumerResults.get(0));
+            verify(mMockDatagramController, never()).sendSatelliteDatagram(anyInt(),
+                    eq(datagramType), eq(datagram), eq(true), any());
+
+            mIIntegerConsumerResults.clear();
+            sendProvisionedStateChangedEvent(true, null);
+            processAllMessages();
+            verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+            mSatelliteControllerUT.sendDatagram(SUB_ID, datagramType, datagram, true,
+                    mIIntegerConsumer);
+            processAllMessages();
+            assertFalse(waitForIIntegerConsumerResult(1));
+            verify(mMockDatagramController, times(1)).sendSatelliteDatagram(anyInt(),
+                    eq(datagramType), eq(datagram), eq(true), any());
+            verify(mMockPointingAppController, times(1)).startPointingUI(eq(true), anyBoolean(),
+                    anyBoolean());
+        }
     }
 
     @Test
@@ -1686,7 +1735,7 @@
     }
 
     @Test
-    public void testSupportedSatelliteServices() {
+    public void testSupportedSatelliteServices() throws Exception {
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
         List<String> satellitePlmnList = mSatelliteControllerUT.getSatellitePlmnsForCarrier(
                 SUB_ID);
@@ -1700,6 +1749,7 @@
                 R.array.config_satellite_providers, satelliteProviderStrArray);
         int[] expectedSupportedServices2 = {2};
         int[] expectedSupportedServices3 = {1, 3};
+        int[] defaultSupportedServices = {5, 6};
         PersistableBundle carrierSupportedSatelliteServicesPerProvider = new PersistableBundle();
         carrierSupportedSatelliteServicesPerProvider.putIntArray(
                 "00102", expectedSupportedServices2);
@@ -1709,6 +1759,9 @@
         mCarrierConfigBundle.putPersistableBundle(CarrierConfigManager
                         .KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE,
                 carrierSupportedSatelliteServicesPerProvider);
+        mCarrierConfigBundle.putIntArray(
+                CarrierConfigManager.KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY,
+                defaultSupportedServices);
         TestSatelliteController testSatelliteController =
                 new TestSatelliteController(mContext, Looper.myLooper(), mFeatureFlags);
 
@@ -1718,6 +1771,9 @@
                 testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00101");
         assertTrue(supportedSatelliteServices.isEmpty());
 
+        // Add entitlement provided PLMNs.
+        setEntitlementPlmnList(testSatelliteController, SUB_ID,
+                Arrays.asList("00102", "00104", "00105"));
         // Carrier config changed with carrierEnabledSatelliteFlag disabled
         for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
                 : mCarrierConfigChangedListenerList) {
@@ -1733,9 +1789,17 @@
         supportedSatelliteServices =
                 testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00103");
         assertTrue(supportedSatelliteServices.isEmpty());
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00104");
+        assertTrue(supportedSatelliteServices.isEmpty());
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00105");
+        assertTrue(supportedSatelliteServices.isEmpty());
 
         // Trigger carrier config changed with carrierEnabledSatelliteFlag enabled
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
         for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
                 : mCarrierConfigChangedListenerList) {
             pair.first.execute(() -> pair.second.onCarrierConfigChanged(
@@ -1749,6 +1813,7 @@
                 expectedSupportedSatellitePlmns, satellitePlmnList.stream().toArray()));
         supportedSatelliteServices =
                 mSatelliteControllerUT.getSupportedSatelliteServices(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)
@@ -1759,6 +1824,19 @@
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
+        // "00104", and "00105" should return default supported service.
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00104");
+        assertTrue(Arrays.equals(defaultSupportedServices,
+                supportedSatelliteServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00105");
+        assertTrue(Arrays.equals(defaultSupportedServices,
+                supportedSatelliteServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
 
         // Subscriptions changed
         int[] newActiveSubIds = {SUB_ID1};
@@ -1773,13 +1851,32 @@
 
         satellitePlmnList = testSatelliteController.getSatellitePlmnsForCarrier(SUB_ID);
         assertTrue(satellitePlmnList.isEmpty());
+        // "00102" and "00103" should return default supported service for SUB_ID.
         supportedSatelliteServices =
                 testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00102");
-        assertTrue(supportedSatelliteServices.isEmpty());
+        assertTrue(Arrays.equals(defaultSupportedServices,
+                supportedSatelliteServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
         supportedSatelliteServices =
                 testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00103");
-        assertTrue(supportedSatelliteServices.isEmpty());
-
+        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");
+        assertTrue(Arrays.equals(defaultSupportedServices,
+                supportedSatelliteServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID, "00105");
+        assertTrue(Arrays.equals(defaultSupportedServices,
+                supportedSatelliteServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
 
         supportedSatelliteServices =
                 testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00102");
@@ -1795,6 +1892,19 @@
                 supportedSatelliteServices.stream()
                         .mapToInt(Integer::intValue)
                         .toArray()));
+        /* "00104", and "00105" should return default supported service. */
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00104");
+        assertTrue(Arrays.equals(defaultSupportedServices,
+                supportedSatelliteServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
+        supportedSatelliteServices =
+                testSatelliteController.getSupportedSatelliteServices(SUB_ID1, "00105");
+        assertTrue(Arrays.equals(defaultSupportedServices,
+                supportedSatelliteServices.stream()
+                        .mapToInt(Integer::intValue)
+                        .toArray()));
     }
 
     @Test
@@ -2154,6 +2264,7 @@
                 SATELLITE_MODEM_STATE_CONNECTED);
 
         resetSatelliteControllerUTToSupportedAndProvisionedState();
+        mSatelliteControllerUT.setSatelliteSessionController(mMockSatelliteSessionController);
         clearInvocations(mMockSatelliteSessionController);
         clearInvocations(mMockDatagramController);
         sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_UNAVAILABLE, null);
@@ -2366,6 +2477,24 @@
         doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
         provisionSatelliteService();
         setUpResponseForStartSendingNtnSignalStrength(expectedResult);
+
+        // but it is ignored because satellite is disabled
+        setUpResponseForRequestIsSatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+        sendCmdStartSendingNtnSignalStrengthChangedEvent(true);
+        processAllMessages();
+        verify(mMockSatelliteModemInterface, never())
+                .startSendingNtnSignalStrength(any(Message.class));
+
+        // after satellite is enabled, startSendingNtnSignalStrength() is requested normally
+        resetSatelliteControllerUT();
+        reset(mMockSatelliteModemInterface);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        provisionSatelliteService();
+        setUpResponseForStartSendingNtnSignalStrength(expectedResult);
+        setUpResponseForRequestIsSatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+        processAllMessages();
         sendCmdStartSendingNtnSignalStrengthChangedEvent(true);
         processAllMessages();
         verify(mMockSatelliteModemInterface, times(1))
@@ -2493,10 +2622,12 @@
     }
 
     @Test
-    public void testCarrierEnabledSatelliteConnectionHysteresisTime() {
+    public void testCarrierEnabledSatelliteConnectionHysteresisTime() throws Exception {
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
         assertFalse(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
 
+        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState2.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
         mCarrierConfigBundle.putInt(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, 1 * 60);
         mCarrierConfigBundle.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
@@ -2506,15 +2637,28 @@
                     /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
             );
         }
+        doReturn(mSignalStrength).when(mPhone).getSignalStrength();
+        doReturn(mSignalStrength).when(mPhone2).getSignalStrength();
+        List<CellSignalStrength> cellSignalStrengthList = new ArrayList<>();
+        cellSignalStrengthList.add(mCellSignalStrength);
+        doReturn(cellSignalStrengthList).when(mSignalStrength).getCellSignalStrengths();
         processAllMessages();
         mSatelliteControllerUT.elapsedRealtime = 0;
         assertFalse(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+        assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone));
+        assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone2));
 
         when(mServiceState.isUsingNonTerrestrialNetwork()).thenReturn(false);
         when(mServiceState2.isUsingNonTerrestrialNetwork()).thenReturn(false);
         sendServiceStateChangedEvent();
         processAllMessages();
         assertFalse(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+        assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone));
+        assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone2));
+        verify(mPhone, times(1)).notifyCarrierRoamingNtnModeChanged(eq(false));
+        verify(mPhone2, times(1)).notifyCarrierRoamingNtnModeChanged(eq(false));
+        clearInvocations(mPhone);
+        clearInvocations(mPhone2);
 
         // Last satellite connected time of Phone2 should be 0
         when(mServiceState2.isUsingNonTerrestrialNetwork()).thenReturn(true);
@@ -2524,6 +2668,12 @@
         mSatelliteControllerUT.elapsedRealtime = 2 * 60 * 1000;
         // But Phone2 is connected to NTN right now
         assertTrue(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+        assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone));
+        assertTrue(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone2));
+        verify(mPhone, times(0)).notifyCarrierRoamingNtnModeChanged(eq(false));
+        verify(mPhone2, times(1)).notifyCarrierRoamingNtnModeChanged(eq(true));
+        clearInvocations(mPhone);
+        clearInvocations(mPhone2);
 
         // Last satellite disconnected time of Phone2 should be 2 * 60 * 1000
         when(mServiceState2.isUsingNonTerrestrialNetwork()).thenReturn(false);
@@ -2531,10 +2681,22 @@
         processAllMessages();
         // Current time (2) - last disconnected time (2) < hysteresis timeout (1)
         assertTrue(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+        assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone));
+        assertTrue(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone2));
+        verify(mPhone, times(0)).notifyCarrierRoamingNtnModeChanged(eq(false));
+        verify(mPhone2, times(0)).notifyCarrierRoamingNtnModeChanged(anyBoolean());
+        clearInvocations(mPhone);
+        clearInvocations(mPhone2);
 
         // Current time (4) - last disconnected time (2) > hysteresis timeout (1)
         mSatelliteControllerUT.elapsedRealtime = 4 * 60 * 1000;
+        moveTimeForward(2 * 60 * 1000);
+        processAllMessages();
         assertFalse(mSatelliteControllerUT.isSatelliteConnectedViaCarrierWithinHysteresisTime());
+        assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone));
+        assertFalse(mSatelliteControllerUT.isInSatelliteModeForCarrierRoaming(mPhone2));
+        verify(mPhone, times(0)).notifyCarrierRoamingNtnModeChanged(eq(false));
+        verify(mPhone2, times(1)).notifyCarrierRoamingNtnModeChanged(eq(false));
     }
 
     @Test
@@ -2667,7 +2829,7 @@
         // Verify call the requestSetSatelliteEnabledForCarrier to enable the satellite when
         // satellite service is enabled by entitlement server.
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, true, new ArrayList<>(),
-                mIIntegerConsumer);
+                new ArrayList<>(), mIIntegerConsumer);
         processAllMessages();
 
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -2687,7 +2849,7 @@
                 .when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
         setUpResponseForRequestSetSatelliteEnabledForCarrier(false, SATELLITE_RESULT_SUCCESS);
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false, new ArrayList<>(),
-                mIIntegerConsumer);
+                new ArrayList<>(), mIIntegerConsumer);
         processAllMessages();
 
         assertTrue(waitForIIntegerConsumerResult(1));
@@ -2701,51 +2863,42 @@
             throws Exception {
         logd("testPassSatellitePlmnToModemAfterUpdateSatelliteEntitlementStatus");
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
         replaceInstance(SatelliteController.class, "mMergedPlmnListPerCarrier",
                 mSatelliteControllerUT, new SparseArray<>());
-        List<String> overlayConfigPlmnList =  new ArrayList<>();
+        List<String> overlayConfigPlmnList = new ArrayList<>();
         replaceInstance(SatelliteController.class, "mSatellitePlmnListFromOverlayConfig",
                 mSatelliteControllerUT, overlayConfigPlmnList);
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
+        mCarrierConfigBundle.putBoolean(CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL,
+                true);
 
-        // If the PlmnListPerCarrier and the overlay config plmn list are empty verify passing to
-        // the modem.
+        // If the entitlement plmn list, the carrier plmn list, the overlay config plmn list and
+        // the barred plmn list are empty, verify not passing to the modem.
+        reset(mMockSatelliteModemInterface);
         List<String> entitlementPlmnList = new ArrayList<>();
+        List<String> barredPlmnList = new ArrayList<>();
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
-                entitlementPlmnList, mIIntegerConsumer);
+                entitlementPlmnList, barredPlmnList, mIIntegerConsumer);
+        verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(anyBoolean(),
+                anyBoolean(), anyBoolean(), any(Message.class));
 
-        List<String> plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(
-                SUB_ID);
-        List<String> allSatellitePlmnList = SatelliteServiceUtils.mergeStrLists(
-                plmnListPerCarrier, overlayConfigPlmnList);
-
-        assertEquals(new ArrayList<>(), plmnListPerCarrier);
-        assertEquals(new ArrayList<>(), allSatellitePlmnList);
-        verify(mMockSatelliteModemInterface, times(1)).setSatellitePlmn(anyInt(),
-                eq(plmnListPerCarrier), eq(allSatellitePlmnList), any(Message.class));
-
-        // If the PlmnListPerCarrier and the overlay config plmn list are exist verify passing
-        // the modem.
+        // If the 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.
+        reset(mMockSatelliteModemInterface);
         entitlementPlmnList = Arrays.stream(new String[]{"00101", "00102", "00103"}).toList();
+        List<String> mergedPlmnList = entitlementPlmnList;
         overlayConfigPlmnList =
                 Arrays.stream(new String[]{"00101", "00102", "00104"}).toList();
         replaceInstance(SatelliteController.class, "mSatellitePlmnListFromOverlayConfig",
                 mSatelliteControllerUT, overlayConfigPlmnList);
+        verifyPassingToModemAfterQueryCompleted(entitlementPlmnList, mergedPlmnList,
+                overlayConfigPlmnList, barredPlmnList);
 
-        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, true,
-                entitlementPlmnList, mIIntegerConsumer);
-
-        plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID);
-        allSatellitePlmnList = SatelliteServiceUtils.mergeStrLists(
-                plmnListPerCarrier, overlayConfigPlmnList);
-
-        assertEquals(entitlementPlmnList, plmnListPerCarrier);
-        verify(mMockSatelliteModemInterface, times(1)).setSatellitePlmn(anyInt(),
-                eq(plmnListPerCarrier), eq(allSatellitePlmnList), any(Message.class));
-
-        // If the PlmnListPerCarrier and the overlay config plmn list are exist verify passing
-        // the modem.
+        // If the entitlement plmn list, the overlay config plmn list and the carrier plmn list
+        // are available and the barred plmn list is empty, verify passing to the modem.
         reset(mMockSatelliteModemInterface);
-        entitlementPlmnList = Arrays.stream(new String[]{"00101", "00102", "00103"}).toList();
         Map<Integer, Map<String, Set<Integer>>>
                 satelliteServicesSupportedByCarriers = new HashMap<>();
         List<String> carrierConfigPlmnList = Arrays.stream(new String[]{"00105", "00106"}).toList();
@@ -2755,19 +2908,80 @@
         satelliteServicesSupportedByCarriers.put(SUB_ID, plmnAndService);
         replaceInstance(SatelliteController.class, "mSatelliteServicesSupportedByCarriers",
                 mSatelliteControllerUT, satelliteServicesSupportedByCarriers);
-        overlayConfigPlmnList = Arrays.stream(new String[]{"00101", "00102", "00104"}).toList();
-        replaceInstance(SatelliteController.class, "mSatellitePlmnListFromOverlayConfig",
-                mSatelliteControllerUT, overlayConfigPlmnList);
-        List<String> mergedPlmnList = entitlementPlmnList;
+        verifyPassingToModemAfterQueryCompleted(entitlementPlmnList, mergedPlmnList,
+                overlayConfigPlmnList, barredPlmnList);
 
-        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, true,
-                entitlementPlmnList, mIIntegerConsumer);
+        // If the entitlement plmn list is empty and the overlay config plmn list and the carrier
+        // plmn list are available, verify passing to the modem.
+        reset(mMockSatelliteModemInterface);
+        entitlementPlmnList = new ArrayList<>();
+        mergedPlmnList = carrierConfigPlmnList;
+        verifyPassingToModemAfterQueryCompleted(entitlementPlmnList, mergedPlmnList,
+                overlayConfigPlmnList, barredPlmnList);
 
-        plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(SUB_ID);
-        allSatellitePlmnList = SatelliteServiceUtils.mergeStrLists(
-                plmnListPerCarrier, overlayConfigPlmnList);
+        // If the entitlement plmn list is empty and the overlay config plmn list, the carrier
+        // plmn list and the barred plmn list are available, verify passing to the modem.
+        reset(mMockSatelliteModemInterface);
+        barredPlmnList = Arrays.stream(new String[]{"00105", "00107"}).toList();
+        verifyPassingToModemAfterQueryCompleted(entitlementPlmnList, mergedPlmnList,
+                overlayConfigPlmnList, barredPlmnList);
+
+        // If the entitlement plmn list is null and the overlay config plmn list and the carrier
+        // plmn list are available, verify passing to the modem.
+        reset(mMockSatelliteModemInterface);
+        entitlementPlmnList = null;
+        mergedPlmnList = carrierConfigPlmnList;
+        verifyPassingToModemAfterQueryCompleted(entitlementPlmnList, mergedPlmnList,
+                overlayConfigPlmnList, barredPlmnList);
+
+        // If the entitlement plmn list is invalid, verify not passing to the modem.
+        reset(mMockSatelliteModemInterface);
+        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));
+
+        // 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));
+
+        // 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));
+
+        // 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));
+    }
+
+    private void verifyPassingToModemAfterQueryCompleted(List<String> entitlementPlmnList,
+            List<String> mergedPlmnList, List<String> overlayConfigPlmnList,
+            List<String> barredPlmnList) {
+        mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false,
+                entitlementPlmnList, barredPlmnList, mIIntegerConsumer);
+
+        List<String> plmnListPerCarrier = mSatelliteControllerUT.getSatellitePlmnsForCarrier(
+                SUB_ID);
+        List<String> allSatellitePlmnList = SatelliteServiceUtils.mergeStrLists(
+                plmnListPerCarrier, overlayConfigPlmnList, barredPlmnList);
 
         assertEquals(mergedPlmnList, plmnListPerCarrier);
+        if (overlayConfigPlmnList.isEmpty()) {
+            assertEquals(plmnListPerCarrier, allSatellitePlmnList);
+        }
         verify(mMockSatelliteModemInterface, times(1)).setSatellitePlmn(anyInt(),
                 eq(plmnListPerCarrier), eq(allSatellitePlmnList), any(Message.class));
     }
@@ -2852,6 +3066,17 @@
                 mSatelliteControllerUT, entitlementPlmnListPerCarrier);
     }
 
+    private void setEntitlementPlmnList(SatelliteController targetClass, int subId,
+            List<String> plmnList) throws Exception {
+        SparseArray<List<String>> entitlementPlmnListPerCarrier = new SparseArray<>();
+        if (!plmnList.isEmpty()) {
+            entitlementPlmnListPerCarrier.clear();
+            entitlementPlmnListPerCarrier.put(subId, plmnList);
+        }
+        replaceInstance(SatelliteController.class, "mEntitlementPlmnListPerCarrier",
+                targetClass, entitlementPlmnListPerCarrier);
+    }
+
     private void setConfigDataPlmnList(List<String> plmnList) {
         doReturn(plmnList).when(mMockConfig).getAllSatellitePlmnsForCarrier(anyInt());
         doReturn(mMockConfig).when(mMockConfigParser).getConfig();
@@ -2895,8 +3120,11 @@
     public void testUpdatePlmnListPerCarrier() throws Exception {
         logd("testUpdatePlmnListPerCarrier");
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+
         replaceInstance(SatelliteController.class, "mMergedPlmnListPerCarrier",
                 mSatelliteControllerUT, new SparseArray<>());
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, true);
         List<String> plmnListPerCarrier;
 
         // verify whether an empty list is returned with conditions below
@@ -2957,21 +3185,21 @@
 
         // Change SUB_ID's EntitlementStatus to true
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, true, new ArrayList<>(),
-                mIIntegerConsumer);
+                new ArrayList<>(), mIIntegerConsumer);
 
         assertEquals(true, satelliteEnabledPerCarrier.get(SUB_ID));
         assertEquals(false, satelliteEnabledPerCarrier.get(SUB_ID1));
 
         // Change SUB_ID1's EntitlementStatus to true
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID1, true, new ArrayList<>(),
-                mIIntegerConsumer);
+                new ArrayList<>(), mIIntegerConsumer);
 
         assertEquals(true, satelliteEnabledPerCarrier.get(SUB_ID));
         assertEquals(true, satelliteEnabledPerCarrier.get(SUB_ID1));
 
         // Change SUB_ID's EntitlementStatus to false
         mSatelliteControllerUT.onSatelliteEntitlementStatusUpdated(SUB_ID, false, new ArrayList<>(),
-                mIIntegerConsumer);
+                new ArrayList<>(), mIIntegerConsumer);
 
         assertEquals(false, satelliteEnabledPerCarrier.get(SUB_ID));
         assertEquals(true, satelliteEnabledPerCarrier.get(SUB_ID1));
@@ -3012,8 +3240,8 @@
         logd("testUpdateEntitlementPlmnListPerCarrier");
         when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
 
-        // If the Satellite entitlement plmn list read from the DB is empty and carrier config
-        // plmn list also is empty , check whether an empty list is returned when calling
+        // If the Satellite entitlement plmn list read from the DB is empty list and carrier
+        // config plmn list also is empty , check whether an empty list is returned when calling
         // getSatellitePlmnsForCarrier before the entitlement query.
         doReturn(new ArrayList<>()).when(
                 mMockSubscriptionManagerService).getSatelliteEntitlementPlmnList(anyInt());
@@ -3140,8 +3368,9 @@
 
         // Successfully disable satellite
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestSatelliteEnabled(false, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -3150,11 +3379,12 @@
         // Time out to enable satellite
         ArgumentCaptor<Message> enableSatelliteResponse = ArgumentCaptor.forClass(Message.class);
         mIIntegerConsumerResults.clear();
-        setUpNoResponseForRequestSatelliteEnabled(true, false);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        setUpNoResponseForRequestSatelliteEnabled(true, false, false);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
-        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(eq(true), eq(false),
+        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(eq(true), eq(false), eq(false),
                 enableSatelliteResponse.capture());
 
         clearInvocations(mMockSatelliteModemInterface);
@@ -3162,8 +3392,8 @@
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_MODEM_TIMEOUT, (long) mIIntegerConsumerResults.get(0));
-        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(eq(false), eq(false), any(
-                Message.class));
+        verify(mMockSatelliteModemInterface).requestSatelliteEnabled(eq(false), eq(false),
+                eq(false), any(Message.class));
         verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
 
         // Send the response for the above request to enable satellite. SatelliteController should
@@ -3176,8 +3406,9 @@
 
         // Successfully enable satellite
         mIIntegerConsumerResults.clear();
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -3187,11 +3418,13 @@
         ArgumentCaptor<Message> disableSatelliteResponse = ArgumentCaptor.forClass(Message.class);
         mIIntegerConsumerResults.clear();
         clearInvocations(mMockSatelliteModemInterface);
-        setUpNoResponseForRequestSatelliteEnabled(false, false);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, mIIntegerConsumer);
+        setUpNoResponseForRequestSatelliteEnabled(false, false, false);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertFalse(waitForIIntegerConsumerResult(1));
         verify(mMockSatelliteModemInterface).requestSatelliteEnabled(eq(false), eq(false),
+                eq(false),
                 disableSatelliteResponse.capture());
 
         clearInvocations(mMockSatelliteModemInterface);
@@ -3200,7 +3433,7 @@
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_MODEM_TIMEOUT, (long) mIIntegerConsumerResults.get(0));
         verify(mMockSatelliteModemInterface, never()).requestSatelliteEnabled(anyBoolean(),
-                anyBoolean(), any(Message.class));
+                anyBoolean(), anyBoolean(), any(Message.class));
         verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
 
         // Send the response for the above request to disable satellite. SatelliteController should
@@ -3212,6 +3445,321 @@
         verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
     }
 
+    @Test
+    public void testUpdateNtnSignalStrentghReportWithFeatureFlagEnabled() {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+
+        mIsSatelliteEnabledSemaphore.drainPermits();
+        mIIntegerConsumerResults.clear();
+        resetSatelliteControllerUT();
+
+        // Successfully provisioned
+        reset(mMockSatelliteModemInterface);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        sendProvisionedStateChangedEvent(true, null);
+        setUpResponseForRequestIsSatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        sendProvisionedStateChangedEvent(true, null);
+        processAllMessages();
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+
+        // startSendingNtnSignalStrength should be invoked when satellite is enabled
+        mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
+        mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+        assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
+        assertTrue(mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled);
+        assertEquals(
+                SATELLITE_MODE_ENABLED_TRUE, mSatelliteControllerUT.satelliteModeSettingValue);
+        verify(mMockSatelliteModemInterface, times(1)).startSendingNtnSignalStrength(
+                any(Message.class));
+
+        // Ignore request ntn signal strength for redundant enable request
+        reset(mMockSatelliteModemInterface);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+        verify(mMockSatelliteModemInterface, never()).startSendingNtnSignalStrength(
+                any(Message.class));
+
+        // stopSendingNtnSignalStrength should be invoked when satellite is successfully off.
+        mIIntegerConsumerResults.clear();
+        reset(mMockSatelliteModemInterface);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
+                mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+        verify(mMockSatelliteModemInterface, times(1)).stopSendingNtnSignalStrength(
+                any(Message.class));
+
+        // Ignore redundant request for stop reporting ntn signal strength.
+        mIIntegerConsumerResults.clear();
+        reset(mMockSatelliteModemInterface);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, false, false, false,
+                mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+        verify(mMockSatelliteModemInterface, never()).stopSendingNtnSignalStrength(
+                any(Message.class));
+
+        // startSendingNtnSignalStrength is invoked when satellite is enabled again.
+        mIIntegerConsumerResults.clear();
+        reset(mMockSatelliteModemInterface);
+        doReturn(true).when(mMockSatelliteModemInterface).isSatelliteServiceSupported();
+        mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled = false;
+        mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled = false;
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+        assertTrue(mSatelliteControllerUT.setSettingsKeyForSatelliteModeCalled);
+        assertTrue(mSatelliteControllerUT.setSettingsKeyToAllowDeviceRotationCalled);
+        assertEquals(
+                SATELLITE_MODE_ENABLED_TRUE, mSatelliteControllerUT.satelliteModeSettingValue);
+        verify(mMockSatelliteModemInterface, times(1)).startSendingNtnSignalStrength(
+                any(Message.class));
+    }
+
+    @Test
+    public void testRegisterForSatelliteSupportedStateChanged_WithFeatureFlagEnabled() {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(true);
+
+        Semaphore semaphore = new Semaphore(0);
+        final boolean[] isSupported  = new boolean[1];
+        ISatelliteSupportedStateCallback callback =
+                new ISatelliteSupportedStateCallback.Stub() {
+                    @Override
+                    public void onSatelliteSupportedStateChanged(boolean supported) {
+                        logd("onSatelliteSupportedStateChanged: supported=" + supported);
+                        isSupported[0] = supported;
+                        try {
+                            semaphore.release();
+                        } catch (Exception ex) {
+                            loge("onSatelliteSupportedStateChanged: Got exception in releasing "
+                                    + "semaphore, ex=" + ex);
+                        }
+                    }
+                };
+
+        resetSatelliteControllerUT();
+        setUpResponseForRequestIsSatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+        int errorCode = mSatelliteControllerUT.registerForSatelliteSupportedStateChanged(
+                SUB_ID, callback);
+        assertEquals(SATELLITE_RESULT_SUCCESS, errorCode);
+
+        sendSatelliteSupportedStateChangedEvent(true, null);
+        processAllMessages();
+        // Verify redundant report is ignored
+        assertFalse(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteSupportedStateChanged"));
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+
+        // Verify updated state is reported
+        sendSatelliteSupportedStateChangedEvent(false, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteSupportedStateChanged"));
+        assertEquals(false, isSupported[0]);
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+
+        // Verify redundant report is ignored
+        sendSatelliteSupportedStateChangedEvent(false, null);
+        processAllMessages();
+        assertFalse(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteSupportedStateChanged"));
+        verifySatelliteSupported(false, SATELLITE_RESULT_SUCCESS);
+
+        // Verify updated state is reported
+        sendSatelliteSupportedStateChangedEvent(true, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteSupportedStateChanged"));
+        assertEquals(true, isSupported[0]);
+        verifySatelliteSupported(true, SATELLITE_RESULT_SUCCESS);
+
+        // Successfully enable satellite
+        sendProvisionedStateChangedEvent(true, null);
+        processAllMessages();
+        verifySatelliteProvisioned(true, SATELLITE_RESULT_SUCCESS);
+        mIIntegerConsumerResults.clear();
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
+        processAllMessages();
+        assertTrue(waitForIIntegerConsumerResult(1));
+        assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
+        verifySatelliteEnabled(true, SATELLITE_RESULT_SUCCESS);
+
+        // Send satellite is not supported state from modem to disable satellite
+        setUpResponseForRequestSatelliteEnabled(false, false, false, SATELLITE_RESULT_SUCCESS);
+        sendSatelliteSupportedStateChangedEvent(false, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteSupportedStateChanged"));
+        assertEquals(false, isSupported[0]);
+
+        // It is needed to set satellite as support to check whether satellite is enabled or not
+        sendSatelliteSupportedStateChangedEvent(true, null);
+        processAllMessages();
+        assertTrue(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteSupportedStateChanged"));
+        assertEquals(true, isSupported[0]);
+        // Verify satellite was disabled
+        verifySatelliteEnabled(false, SATELLITE_RESULT_SUCCESS);
+
+        mSatelliteControllerUT.unregisterForSatelliteSupportedStateChanged(SUB_ID, callback);
+        sendSatelliteSupportedStateChangedEvent(true, null);
+        processAllMessages();
+        assertFalse(waitForForEvents(
+                semaphore, 1, "testRegisterForSatelliteSupportedStateChanged"));
+    }
+
+    @Test
+    public void testRegisterForSatelliteSupportedStateChanged_WithFeatureFlagDisabled() {
+        when(mFeatureFlags.oemEnabledSatelliteFlag()).thenReturn(false);
+
+        Semaphore semaphore = new Semaphore(0);
+        ISatelliteSupportedStateCallback callback =
+                new ISatelliteSupportedStateCallback.Stub() {
+                    @Override
+                    public void onSatelliteSupportedStateChanged(boolean supported) {
+                        logd("onSatelliteSupportedStateChanged: supported=" + supported);
+                        try {
+                            semaphore.release();
+                        } catch (Exception ex) {
+                            loge("onSatelliteSupportedStateChanged: Got exception in releasing "
+                                    + "semaphore, ex=" + ex);
+                        }
+                    }
+                };
+        int errorCode = mSatelliteControllerUT.registerForSatelliteSupportedStateChanged(
+                SUB_ID, callback);
+        assertEquals(SATELLITE_RESULT_REQUEST_NOT_SUPPORTED, errorCode);
+    }
+
+    @Test
+    public void testIsSatelliteEmergencyMessagingSupportedViaCarrier() {
+        // Carrier-enabled flag is off
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
+        assertFalse(mSatelliteControllerUT.isSatelliteEmergencyMessagingSupportedViaCarrier());
+
+        // Carrier-enabled flag is on and satellite attach is not supported
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        assertFalse(mSatelliteControllerUT.isSatelliteEmergencyMessagingSupportedViaCarrier());
+
+        // Trigger carrier config changed to enable satellite attach
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+        assertFalse(mSatelliteControllerUT.isSatelliteEmergencyMessagingSupportedViaCarrier());
+
+        // Trigger carrier config changed to enable satellite attach & emergency messaging
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
+        mCarrierConfigBundle.putBoolean(
+                CarrierConfigManager.KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL, true);
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+        assertTrue(mSatelliteControllerUT.isSatelliteEmergencyMessagingSupportedViaCarrier());
+    }
+
+    @Test
+    public void testGetCarrierEmergencyCallWaitForConnectionTimeoutMillis() {
+        // Carrier-enabled flag is off
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(false);
+        assertEquals(DEFAULT_CARRIER_EMERGENCY_CALL_WAIT_FOR_CONNECTION_TIMEOUT_MILLIS,
+                mSatelliteControllerUT.getCarrierEmergencyCallWaitForConnectionTimeoutMillis());
+
+        // Carrier-enabled flag is on
+        when(mFeatureFlags.carrierEnabledSatelliteFlag()).thenReturn(true);
+        assertEquals(DEFAULT_CARRIER_EMERGENCY_CALL_WAIT_FOR_CONNECTION_TIMEOUT_MILLIS,
+                mSatelliteControllerUT.getCarrierEmergencyCallWaitForConnectionTimeoutMillis());
+
+        // Trigger carrier config changed to enable satellite attach
+        int timeoutMillisForCarrier1 = 1000;
+        PersistableBundle carrierConfigBundle1 = new PersistableBundle();
+        carrierConfigBundle1.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
+        carrierConfigBundle1.putBoolean(
+                CarrierConfigManager.KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL, true);
+        carrierConfigBundle1.putInt(
+                KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
+                timeoutMillisForCarrier1);
+        doReturn(carrierConfigBundle1)
+                .when(mCarrierConfigManager).getConfigForSubId(eq(SUB_ID), anyVararg());
+
+        int timeoutMillisForCarrier2 = 2000;
+        PersistableBundle carrierConfigBundle2 = new PersistableBundle();
+        carrierConfigBundle2.putBoolean(
+                CarrierConfigManager.KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, true);
+        carrierConfigBundle2.putBoolean(
+                CarrierConfigManager.KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL, true);
+        carrierConfigBundle2.putInt(
+                KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
+                timeoutMillisForCarrier2);
+        doReturn(carrierConfigBundle2)
+                .when(mCarrierConfigManager).getConfigForSubId(eq(SUB_ID1), anyVararg());
+
+        for (Pair<Executor, CarrierConfigManager.CarrierConfigChangeListener> pair
+                : mCarrierConfigChangedListenerList) {
+            pair.first.execute(() -> pair.second.onCarrierConfigChanged(
+                    /*slotIndex*/ 0, /*subId*/ SUB_ID, /*carrierId*/ 0, /*specificCarrierId*/ 0)
+            );
+        }
+        processAllMessages();
+
+        // Both phones are not in satellite mode for carrier roaming, and thus the max timeout
+        // duration - timeoutMillisForCarrier2 - is used
+        assertEquals(timeoutMillisForCarrier2,
+                mSatelliteControllerUT.getCarrierEmergencyCallWaitForConnectionTimeoutMillis());
+
+        // Phone 1 is in satellite mode for carrier roaming
+        when(mServiceState.isUsingNonTerrestrialNetwork()).thenReturn(true);
+        assertEquals(timeoutMillisForCarrier1,
+                mSatelliteControllerUT.getCarrierEmergencyCallWaitForConnectionTimeoutMillis());
+
+        // Both phones are in satellite mode for carrier roaming. The timeout duration of the first
+        // phone will be selected
+        when(mServiceState2.isUsingNonTerrestrialNetwork()).thenReturn(true);
+        assertEquals(timeoutMillisForCarrier1,
+                mSatelliteControllerUT.getCarrierEmergencyCallWaitForConnectionTimeoutMillis());
+    }
+
     private void resetSatelliteControllerUTEnabledState() {
         logd("resetSatelliteControllerUTEnabledState");
         setUpResponseForRequestIsSatelliteSupported(false, SATELLITE_RESULT_RADIO_NOT_AVAILABLE);
@@ -3263,8 +3811,9 @@
         setRadioPower(true);
         processAllMessages();
 
-        setUpResponseForRequestSatelliteEnabled(true, false, SATELLITE_RESULT_SUCCESS);
-        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, mIIntegerConsumer);
+        setUpResponseForRequestSatelliteEnabled(true, false, false, SATELLITE_RESULT_SUCCESS);
+        mSatelliteControllerUT.requestSatelliteEnabled(SUB_ID, true, false, false,
+                mIIntegerConsumer);
         processAllMessages();
         assertTrue(waitForIIntegerConsumerResult(1));
         assertEquals(SATELLITE_RESULT_SUCCESS, (long) mIIntegerConsumerResults.get(0));
@@ -3296,32 +3845,6 @@
         }).when(mMockSatelliteModemInterface).requestIsSatelliteSupported(any(Message.class));
     }
 
-    private void setUpResponseForRequestIsSatelliteAllowedForCurrentLocation(
-            boolean isSatelliteAllowed, @SatelliteManager.SatelliteResult int error) {
-        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
-                ? null : new SatelliteException(error);
-        doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[0];
-            AsyncResult.forMessage(message, isSatelliteAllowed, exception);
-            message.sendToTarget();
-            return null;
-        }).when(mMockSatelliteModemInterface)
-                .requestIsSatelliteCommunicationAllowedForCurrentLocation(any(Message.class));
-    }
-
-    private void setUpNullResponseForRequestIsSatelliteAllowedForCurrentLocation(
-            @SatelliteManager.SatelliteResult int error) {
-        SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
-                ? null : new SatelliteException(error);
-        doAnswer(invocation -> {
-            Message message = (Message) invocation.getArguments()[0];
-            AsyncResult.forMessage(message, null, exception);
-            message.sendToTarget();
-            return null;
-        }).when(mMockSatelliteModemInterface)
-                .requestIsSatelliteCommunicationAllowedForCurrentLocation(any(Message.class));
-    }
-
     private void setUpResponseForRequestTimeForNextSatelliteVisibility(
             int satelliteVisibilityTime, @SatelliteManager.SatelliteResult int error) {
         SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
@@ -3363,19 +3886,21 @@
     }
 
     private void setUpResponseForRequestSatelliteEnabled(
-            boolean enabled, boolean demoMode, @SatelliteManager.SatelliteResult int error) {
+            boolean enabled, boolean demoMode, boolean emergency,
+            @SatelliteManager.SatelliteResult int error) {
         SatelliteException exception = (error == SATELLITE_RESULT_SUCCESS)
                 ? null : new SatelliteException(error);
         doAnswer(invocation -> {
             if (exception == null && !enabled) {
                 sendSatelliteModemStateChangedEvent(SATELLITE_MODEM_STATE_OFF, null);
             }
-            Message message = (Message) invocation.getArguments()[2];
+            Message message = (Message) invocation.getArguments()[3];
             AsyncResult.forMessage(message, null, exception);
             message.sendToTarget();
             return null;
         }).when(mMockSatelliteModemInterface)
-                .requestSatelliteEnabled(eq(enabled), eq(demoMode), any(Message.class));
+                .requestSatelliteEnabled(eq(enabled), eq(demoMode), eq(emergency),
+                        any(Message.class));
     }
 
     private void setUpResponseForRequestSetSatelliteEnabledForCarrier(
@@ -3391,9 +3916,11 @@
                 .requestSetSatelliteEnabledForCarrier(anyInt(), eq(enabled), any(Message.class));
     }
 
-    private void setUpNoResponseForRequestSatelliteEnabled(boolean enabled, boolean demoMode) {
+    private void setUpNoResponseForRequestSatelliteEnabled(boolean enabled, boolean demoMode,
+            boolean emergency) {
         doNothing().when(mMockSatelliteModemInterface)
-                .requestSatelliteEnabled(eq(enabled), eq(demoMode), any(Message.class));
+                .requestSatelliteEnabled(eq(enabled), eq(demoMode), eq(emergency),
+                        any(Message.class));
     }
 
     private void setUpResponseForProvisionSatelliteService(
@@ -3545,24 +4072,6 @@
         return true;
     }
 
-    private boolean waitForRequestIsSatelliteAllowedForCurrentLocationResult(
-            int expectedNumberOfEvents) {
-        for (int i = 0; i < expectedNumberOfEvents; i++) {
-            try {
-                if (!mSatelliteAllowedSemaphore.tryAcquire(TIMEOUT, TimeUnit.MILLISECONDS)) {
-                    loge("Timeout to receive "
-                            + "requestIsCommunicationAllowedForCurrentLocation()"
-                            + " callback");
-                    return false;
-                }
-            } catch (Exception ex) {
-                loge("waitForRequestIsSatelliteSupportedResult: Got exception=" + ex);
-                return false;
-            }
-        }
-        return true;
-    }
-
     private boolean waitForRequestTimeForNextSatelliteVisibilityResult(
             int expectedNumberOfEvents) {
         for (int i = 0; i < expectedNumberOfEvents; i++) {
@@ -3773,6 +4282,13 @@
         msg.sendToTarget();
     }
 
+    private void sendSatelliteSupportedStateChangedEvent(boolean supported, Throwable exception) {
+        Message msg = mSatelliteControllerUT.obtainMessage(
+                41 /* EVENT_SATELLITE_SUPPORTED_STATE_CHANGED */);
+        msg.obj = new AsyncResult(null, supported, exception);
+        msg.sendToTarget();
+    }
+
     private void setRadioPower(boolean on) {
         mSimulatedCommands.setRadioPower(on, false, false, null);
     }
@@ -3975,6 +4491,7 @@
         public boolean allRadiosDisabled = true;
         public long elapsedRealtime = 0;
         public int satelliteModeSettingValue = SATELLITE_MODE_ENABLED_FALSE;
+        public boolean setSettingsKeyToAllowDeviceRotationCalled = false;
 
         TestSatelliteController(
                 Context context, Looper looper, @NonNull FeatureFlags featureFlags) {
@@ -3995,6 +4512,12 @@
         }
 
         @Override
+        protected void setSettingsKeyToAllowDeviceRotation(int val) {
+            logd("setSettingsKeyToAllowDeviceRotation: val=" + val);
+            setSettingsKeyToAllowDeviceRotationCalled = true;
+        }
+
+        @Override
         protected boolean areAllRadiosDisabled() {
             return allRadiosDisabled;
         }
@@ -4010,5 +4533,9 @@
         protected long getElapsedRealtime() {
             return elapsedRealtime;
         }
+
+        void setSatelliteSessionController(SatelliteSessionController satelliteSessionController) {
+            mSatelliteSessionController = satelliteSessionController;
+        }
     }
 }
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 d12828a..876fc51 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSOSMessageRecommenderTest.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.telephony.satellite;
 
+import static android.telephony.ServiceState.STATE_IN_SERVICE;
+import static android.telephony.ServiceState.STATE_OUT_OF_SERVICE;
 import static android.telephony.TelephonyManager.EXTRA_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE;
 import static android.telephony.TelephonyManager.EXTRA_EMERGENCY_CALL_TO_SATELLITE_LAUNCH_INTENT;
 import static android.telephony.satellite.SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_SOS;
@@ -38,11 +40,13 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Looper;
 import android.os.OutcomeReceiver;
 import android.os.RemoteException;
 import android.telecom.Connection;
+import android.telecom.TelecomManager;
 import android.telephony.BinderCacheManager;
 import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
@@ -86,7 +90,7 @@
 @TestableLooper.RunWithLooper
 public class SatelliteSOSMessageRecommenderTest extends TelephonyTest {
     private static final String TAG = "SatelliteSOSMessageRecommenderTest";
-    private static final long TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS = 500;
+    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 String CALL_ID = "CALL_ID";
@@ -96,6 +100,7 @@
             "android.com.google.default.SmsMmsApp";
     private static final String DEFAULT_HANDOVER_INTENT_ACTION =
             "android.com.vendor.action.EMERGENCY_MESSAGING";
+    private static final String DEFAULT_T911_HANDOVER_INTENT_ACTION = Intent.ACTION_SENDTO;
     private TestSatelliteController mTestSatelliteController;
     private TestImsManager mTestImsManager;
     @Mock
@@ -105,6 +110,7 @@
     @Mock
     private FeatureFlags mFeatureFlags;
     private TestConnection mTestConnection;
+    private Uri mTestConnectionAddress = Uri.parse("tel:1234");
     private TestSOSMessageRecommender mTestSOSMessageRecommender;
     private ServiceState mServiceState2;
 
@@ -118,12 +124,16 @@
                 .thenReturn("");
         when(mResources.getString(R.string.config_satellite_emergency_handover_intent_action))
                 .thenReturn(DEFAULT_HANDOVER_INTENT_ACTION);
+        when(mResources.getInteger(
+                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);
         mTestSatelliteController = new TestSatelliteController(mContext,
                 Looper.myLooper(), mFeatureFlags);
         mTestImsManager = new TestImsManager(
                 mContext, PHONE_ID, mMmTelFeatureConnectionFactory, null, null, null);
         mTestConnection = new TestConnection(CALL_ID);
+        mTestConnection.setAddress(mTestConnectionAddress, TelecomManager.PRESENTATION_ALLOWED);
         mPhones = new Phone[] {mPhone, mPhone2};
         replaceInstance(PhoneFactory.class, "sPhones", null, mPhones);
         mServiceState2 = Mockito.mock(ServiceState.class);
@@ -132,10 +142,9 @@
         when(mPhone2.getServiceState()).thenReturn(mServiceState2);
         when(mPhone2.getPhoneId()).thenReturn(PHONE_ID2);
         mTestSOSMessageRecommender = new TestSOSMessageRecommender(mContext, Looper.myLooper(),
-                mTestSatelliteController, mTestImsManager,
-                TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
-        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState2.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+                mTestSatelliteController, mTestImsManager);
+        when(mServiceState.getState()).thenReturn(STATE_OUT_OF_SERVICE);
+        when(mServiceState2.getState()).thenReturn(STATE_OUT_OF_SERVICE);
         when(mPhone.isImsRegistered()).thenReturn(false);
         when(mPhone2.isImsRegistered()).thenReturn(false);
     }
@@ -149,7 +158,7 @@
     public void testTimeoutBeforeEmergencyCallEnd_T911() {
         testTimeoutBeforeEmergencyCallEnd(EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911,
                 DEFAULT_SATELLITE_MESSAGING_PACKAGE, DEFAULT_SATELLITE_MESSAGING_CLASS,
-                DEFAULT_HANDOVER_INTENT_ACTION);
+                DEFAULT_T911_HANDOVER_INTENT_ACTION);
     }
 
     @Test
@@ -267,7 +276,7 @@
         assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
                 EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911,
                 DEFAULT_SATELLITE_MESSAGING_PACKAGE, DEFAULT_SATELLITE_MESSAGING_CLASS,
-                DEFAULT_HANDOVER_INTENT_ACTION));
+                DEFAULT_T911_HANDOVER_INTENT_ACTION));
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
         mTestSatelliteController.isOemEnabledSatelliteSupported = true;
@@ -284,7 +293,7 @@
     }
 
     @Test
-    public void testImsRegistrationStateChangedBeforeTimeout() {
+    public void testNetworkStateChangedBeforeTimeout() {
         mTestSOSMessageRecommender.isSatelliteAllowedCallback = null;
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
@@ -295,6 +304,7 @@
         assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
 
         when(mPhone.isImsRegistered()).thenReturn(true);
+        when(mServiceState.getState()).thenReturn(STATE_IN_SERVICE);
         mTestImsManager.sendImsRegistrationStateChangedEvent(0, true);
         processAllMessages();
 
@@ -304,18 +314,30 @@
         assertUnregisterForStateChangedEventsTriggered(mPhone, 0, 0, 0);
 
         when(mPhone.isImsRegistered()).thenReturn(false);
+        when(mServiceState.getState()).thenReturn(STATE_OUT_OF_SERVICE);
+        mTestImsManager.sendImsRegistrationStateChangedEvent(0, true);
+        processAllMessages();
+
+        assertFalse(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE));
+        assertTrue(mTestSOSMessageRecommender.isTimerStarted());
+        assertEquals(2, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertUnregisterForStateChangedEventsTriggered(mPhone, 0, 0, 0);
+
+        when(mPhone.isImsRegistered()).thenReturn(false);
         when(mPhone2.isImsRegistered()).thenReturn(true);
+        when(mServiceState.getState()).thenReturn(STATE_IN_SERVICE);
         mTestImsManager.sendImsRegistrationStateChangedEvent(1, true);
         processAllMessages();
         assertFalse(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE));
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
-        assertEquals(1, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertEquals(2, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertUnregisterForStateChangedEventsTriggered(mPhone, 0, 0, 0);
 
         when(mPhone2.isImsRegistered()).thenReturn(false);
+        when(mServiceState.getState()).thenReturn(STATE_OUT_OF_SERVICE);
         mTestImsManager.sendImsRegistrationStateChangedEvent(1, false);
         processAllMessages();
-        assertEquals(2, mTestSOSMessageRecommender.getCountOfTimerStarted());
+        assertEquals(3, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertNull(mTestSOSMessageRecommender.isSatelliteAllowedCallback);
 
         // Move Location service to emergency mode
@@ -331,7 +353,7 @@
 
         assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
                 EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911, DEFAULT_SATELLITE_MESSAGING_PACKAGE,
-                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_HANDOVER_INTENT_ACTION));
+                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_T911_HANDOVER_INTENT_ACTION));
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
@@ -380,7 +402,7 @@
 
         assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
                 EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911, DEFAULT_SATELLITE_MESSAGING_PACKAGE,
-                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_HANDOVER_INTENT_ACTION));
+                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_T911_HANDOVER_INTENT_ACTION));
         assertFalse(mTestSOSMessageRecommender.isTimerStarted());
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
         assertUnregisterForStateChangedEventsTriggered(mPhone, 2, 4, 2);
@@ -419,7 +441,7 @@
 
         assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
                 EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911, DEFAULT_SATELLITE_MESSAGING_PACKAGE,
-                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_HANDOVER_INTENT_ACTION));
+                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_T911_HANDOVER_INTENT_ACTION));
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
@@ -429,7 +451,7 @@
     @Test
     public void testCellularServiceStateChangedBeforeTimeout_InServiceToOutOfService() {
         testCellularServiceStateChangedBeforeTimeout(
-                ServiceState.STATE_IN_SERVICE, ServiceState.STATE_OUT_OF_SERVICE);
+                ServiceState.STATE_IN_SERVICE, STATE_OUT_OF_SERVICE);
     }
 
     @Test
@@ -441,7 +463,7 @@
     @Test
     public void testCellularServiceStateChangedBeforeTimeout_EmergencyOnlyToOutOfService() {
         testCellularServiceStateChangedBeforeTimeout(
-                ServiceState.STATE_EMERGENCY_ONLY, ServiceState.STATE_OUT_OF_SERVICE);
+                ServiceState.STATE_EMERGENCY_ONLY, STATE_OUT_OF_SERVICE);
     }
 
     @Test
@@ -505,8 +527,7 @@
         TestSOSMessageRecommender testSOSMessageRecommender = new TestSOSMessageRecommender(
                 mContext,
                 Looper.myLooper(),
-                satelliteController, mTestImsManager,
-                TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS);
+                satelliteController, mTestImsManager);
         testSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
         processAllMessages();
 
@@ -532,6 +553,32 @@
                 originalIsSatelliteViaOemProvisioned;
     }
 
+    @Test
+    public void testSelectEmergencyCallWaitForConnectionTimeoutDuration() {
+        // Both OEM and carrier don't support satellite
+        mTestSatelliteController.isSatelliteEmergencyMessagingSupportedViaCarrier = false;
+        mTestSatelliteController.isOemEnabledSatelliteSupported = false;
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        processAllMessages();
+        assertEquals(0, mTestSOSMessageRecommender.getTimeOutMillis());
+
+        // Only OEM support satellite
+        mTestSatelliteController.isOemEnabledSatelliteSupported = true;
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        processAllMessages();
+        assertEquals(TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS,
+                mTestSOSMessageRecommender.getTimeOutMillis());
+
+        // Both OEM and carrier support satellite. Thus, carrier's timeout duration will be used
+        long carrierTimeoutMillis = 1000;
+        mTestSatelliteController.isSatelliteEmergencyMessagingSupportedViaCarrier = true;
+        mTestSatelliteController.carrierEmergencyCallWaitForConnectionTimeoutMillis =
+                carrierTimeoutMillis;
+        mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
+        processAllMessages();
+        assertEquals(carrierTimeoutMillis, mTestSOSMessageRecommender.getTimeOutMillis());
+    }
+
     private void testStopTrackingCallBeforeTimeout(
             @Connection.ConnectionState int connectionState) {
         mTestSOSMessageRecommender.onEmergencyCallStarted(mTestConnection);
@@ -599,7 +646,7 @@
 
         assertTrue(mTestConnection.isEventSent(TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE,
                 EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911, DEFAULT_SATELLITE_MESSAGING_PACKAGE,
-                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_HANDOVER_INTENT_ACTION));
+                DEFAULT_SATELLITE_MESSAGING_CLASS, DEFAULT_T911_HANDOVER_INTENT_ACTION));
         assertUnregisterForStateChangedEventsTriggered(mPhone, 1, 2, 1);
         assertUnregisterForStateChangedEventsTriggered(mPhone2, 1, 2, 1);
         assertEquals(0, mTestSOSMessageRecommender.getCountOfTimerStarted());
@@ -636,6 +683,9 @@
         private boolean mIsSatelliteConnectedViaCarrierWithinHysteresisTime = true;
         public boolean isOemEnabledSatelliteSupported = true;
         public boolean isCarrierEnabledSatelliteSupported = true;
+        public boolean isSatelliteEmergencyMessagingSupportedViaCarrier = true;
+        public long carrierEmergencyCallWaitForConnectionTimeoutMillis =
+                TEST_EMERGENCY_CALL_TO_SOS_MSG_HYSTERESIS_TIMEOUT_MILLIS;
 
         /**
          * Create a SatelliteController to act as a backend service of
@@ -696,6 +746,16 @@
             return INVALID_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE;
         }
 
+        @Override
+        public boolean isSatelliteEmergencyMessagingSupportedViaCarrier() {
+            return isSatelliteEmergencyMessagingSupportedViaCarrier;
+        }
+
+        @Override
+        public long getCarrierEmergencyCallWaitForConnectionTimeoutMillis() {
+            return carrierEmergencyCallWaitForConnectionTimeoutMillis;
+        }
+
         public void setSatelliteConnectedViaCarrierWithinHysteresisTime(
                 boolean connectedViaCarrier) {
             mIsSatelliteConnectedViaCarrierWithinHysteresisTime = connectedViaCarrier;
@@ -811,12 +871,10 @@
          * @param imsManager          The ImsManager instance associated with the phone, which is
          *                            used for making the emergency call. This argument is not
          *                            null only in unit tests.
-         * @param timeoutMillis       The timeout duration of the timer.
          */
         TestSOSMessageRecommender(Context context, Looper looper,
-                SatelliteController satelliteController, ImsManager imsManager,
-                long timeoutMillis) {
-            super(context, looper, satelliteController, imsManager, timeoutMillis);
+                SatelliteController satelliteController, ImsManager imsManager) {
+            super(context, looper, satelliteController, imsManager);
         }
 
         @Override
@@ -843,6 +901,10 @@
         public void sendServiceStateChangedEvent() {
             sendMessage(obtainMessage(EVENT_SERVICE_STATE_CHANGED));
         }
+
+        public long getTimeOutMillis() {
+            return mTimeoutMillis;
+        }
     }
 
     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 28874df..0e4adcd 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteServiceUtilsTest.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.satellite;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.os.PersistableBundle;
@@ -60,6 +61,8 @@
         String plmn1 = "10101";
         String plmn2 = "10102";
         String plmn3 = "10103";
+        String plmn4 = "";
+        String plmn5 = "123456789";
         int[] supportedServicesForPlmn1 = {1, 2, 3};
         int[] supportedServicesForPlmn2 = {3, 4, 100};
         int[] expectedServicesForPlmn1 = {1, 2, 3};
@@ -74,6 +77,8 @@
         supportedServicesBundle.putIntArray(plmn1, supportedServicesForPlmn1);
         supportedServicesBundle.putIntArray(plmn2, supportedServicesForPlmn2);
         supportedServicesBundle.putIntArray(plmn3, new int[0]);
+        supportedServicesBundle.putIntArray(plmn4, supportedServicesForPlmn1);
+        supportedServicesBundle.putIntArray(plmn5, supportedServicesForPlmn2);
 
         supportedServiceMap =
                 SatelliteServiceUtils.parseSupportedSatelliteServices(supportedServicesBundle);
@@ -96,6 +101,9 @@
         assertTrue(supportedServiceMap.containsKey(plmn3));
         supportedServices = supportedServiceMap.get(plmn3);
         assertTrue(supportedServices.isEmpty());
+
+        assertFalse(supportedServiceMap.containsKey(plmn4));
+        assertFalse(supportedServiceMap.containsKey(plmn5));
     }
 
     @Test
@@ -105,5 +113,10 @@
         List<String> expectedMergedList = Arrays.asList("1", "2", "3");
         List<String> mergedList = SatelliteServiceUtils.mergeStrLists(l1, l2);
         assertEquals(expectedMergedList, mergedList);
+
+        List<String> l3 = Arrays.asList("2", "3", "4");
+        expectedMergedList = Arrays.asList("1", "2", "3", "4");
+        mergedList = SatelliteServiceUtils.mergeStrLists(l1, l2, l3);
+        assertEquals(expectedMergedList, mergedList);
     }
 }
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 a1c2cfc..78763d1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/satellite/SatelliteSessionControllerTest.java
@@ -45,6 +45,7 @@
 import android.testing.TestableLooper;
 
 import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.flags.FeatureFlags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -53,6 +54,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -69,6 +72,8 @@
 
     private static final String STATE_UNAVAILABLE = "UnavailableState";
     private static final String STATE_POWER_OFF = "PowerOffState";
+    private static final String STATE_ENABLING_SATELLITE = "EnablingState";
+    private static final String STATE_DISABLING_SATELLITE = "DisablingState";
     private static final String STATE_IDLE = "IdleState";
     private static final String STATE_TRANSFERRING = "TransferringState";
     private static final String STATE_LISTENING = "ListeningState";
@@ -101,11 +106,12 @@
         Resources resources = mContext.getResources();
         when(resources.getInteger(anyInt())).thenReturn(TEST_SATELLITE_TIMEOUT_MILLIS);
 
+        when(mFeatureFlags.satellitePersistentLogging()).thenReturn(true);
         when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(false);
         mSatelliteModemInterface = new TestSatelliteModemInterface(
-                mContext, mMockSatelliteController, Looper.myLooper());
+                mContext, mMockSatelliteController, Looper.myLooper(), mFeatureFlags);
         mTestSatelliteSessionController = new TestSatelliteSessionController(mContext,
-                Looper.myLooper(), true, mSatelliteModemInterface);
+                Looper.myLooper(), mFeatureFlags, true, mSatelliteModemInterface);
         processAllMessages();
 
         mTestSatelliteModemStateCallback = new TestSatelliteModemStateCallback();
@@ -127,7 +133,7 @@
          * state.
          */
         TestSatelliteSessionController sessionController1 = new TestSatelliteSessionController(
-                mContext, Looper.myLooper(), false, mSatelliteModemInterface);
+                mContext, Looper.myLooper(), mFeatureFlags, false, mSatelliteModemInterface);
         assertNotNull(sessionController1);
         processAllMessages();
         assertEquals(STATE_UNAVAILABLE, sessionController1.getCurrentStateName());
@@ -136,7 +142,7 @@
          * Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
          */
         TestSatelliteSessionController sessionController2 = new TestSatelliteSessionController(
-                mContext, Looper.myLooper(), true, mSatelliteModemInterface);
+                mContext, Looper.myLooper(), mFeatureFlags, true, mSatelliteModemInterface);
         assertNotNull(sessionController2);
         processAllMessages();
         assertEquals(STATE_POWER_OFF, sessionController2.getCurrentStateName());
@@ -149,7 +155,7 @@
          * state.
          */
         TestSatelliteSessionController sessionController = new TestSatelliteSessionController(
-                mContext, Looper.myLooper(), false, mSatelliteModemInterface);
+                mContext, Looper.myLooper(), mFeatureFlags, false, mSatelliteModemInterface);
         assertNotNull(sessionController);
         processAllMessages();
         assertEquals(STATE_UNAVAILABLE, sessionController.getCurrentStateName());
@@ -171,9 +177,7 @@
         assertNotNull(mTestSatelliteSessionController);
         assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
 
-        // Power on the modem.
-        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
-        processAllMessages();
+        powerOnSatelliteModem();
 
         // SatelliteSessionController should move to IDLE state after the modem is powered on.
         assertSuccessfulModemStateChangedCallback(
@@ -191,9 +195,7 @@
         assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
         assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
 
-        // Power on the modem.
-        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
-        processAllMessages();
+        powerOnSatelliteModem();
 
         // SatelliteSessionController should move to IDLE state after radio is turned on.
         assertSuccessfulModemStateChangedCallback(
@@ -414,9 +416,7 @@
         assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
         setupDatagramTransferringState(false);
 
-        // Power on the modem.
-        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
-        processAllMessages();
+        powerOnSatelliteModem();
 
         // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
         // is powered on.
@@ -448,9 +448,7 @@
                 SatelliteManager.SATELLITE_MODEM_STATE_OFF);
         clearInvocations(mMockDatagramController);
 
-        // Power on the modem.
-        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
-        processAllMessages();
+        powerOnSatelliteModem();
 
         // SatelliteSessionController should move to NOT_CONNECTED state after radio is turned on.
         assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
@@ -679,9 +677,7 @@
                 SatelliteManager.SATELLITE_MODEM_STATE_OFF);
         clearInvocations(mMockDatagramController);
 
-        // Power on the modem.
-        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
-        processAllMessages();
+        powerOnSatelliteModem();
 
         // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
         // is powered on.
@@ -736,9 +732,7 @@
                 mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
         assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
 
-        // Power on the modem.
-        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
-        processAllMessages();
+        powerOnSatelliteModem();
 
         // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
         // is powered on.
@@ -764,9 +758,7 @@
                 mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
         assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
 
-        // Power on the modem.
-        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
-        processAllMessages();
+        powerOnSatelliteModem();
 
         // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
         // is powered on.
@@ -804,6 +796,194 @@
         assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
                 SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
         assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Power off the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false);
+        processAllMessages();
+
+        // SatelliteSessionController should move to POWER_OFF
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        moveSatelliteToEnablingState();
+
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        processAllMessages();
+
+        // The modem state changed events should be deferred
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(
+                STATE_ENABLING_SATELLITE, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isEventDeferred(
+                4 /* EVENT_SATELLITE_MODEM_STATE_CHANGED */));
+
+        // Modem is powered on
+        mTestSatelliteModemStateCallback.clearModemStates();
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
+        processAllMessages();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on. Then, it should move to CONNECTED and then back to NOT_CONNECTED state
+        // because of the above deferred events.
+        assertEquals(3, mTestSatelliteModemStateCallback.getNumberOfModemStates());
+        assertEquals(SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED,
+                mTestSatelliteModemStateCallback.getModemState(0));
+        assertEquals(SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED,
+                mTestSatelliteModemStateCallback.getModemState(1));
+        assertEquals(SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED,
+                mTestSatelliteModemStateCallback.getModemState(2));
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Power off the modem.
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false);
+        processAllMessages();
+
+        // SatelliteSessionController should move to POWER_OFF
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        moveSatelliteToEnablingState();
+
+        mTestSatelliteModemStateCallback.clearSemaphorePermits();
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        processAllMessages();
+
+        // The modem state changed events should be deferred
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(
+                STATE_ENABLING_SATELLITE, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isEventDeferred(
+                4 /* EVENT_SATELLITE_MODEM_STATE_CHANGED */));
+
+        // Modem got reset. The deferred messages should be removed.
+        mTestSatelliteModemStateCallback.clearSemaphorePermits();
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false);
+        processAllMessages();
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isEventDeferred(
+                4 /* EVENT_SATELLITE_MODEM_STATE_CHANGED */));
+
+        powerOnSatelliteModem();
+
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(1, mTestSatelliteModemStateCallback.getNumberOfModemStates());
+        assertEquals(SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED,
+                mTestSatelliteModemStateCallback.getModemState(0));
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+    }
+
+    @Test
+    public void testEnablingSatellite() {
+        /*
+         * Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+         */
+        assertNotNull(mTestSatelliteSessionController);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Power off satellite
+        mTestSatelliteSessionController.onSatelliteEnablementStarted(false);
+        processAllMessages();
+
+        // Satellite should stay at POWER_OFF state
+        assertModemStateChangedCallbackNotCalled(mTestSatelliteModemStateCallback);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        moveSatelliteToEnablingState();
+
+        // Satellite enablement has failed
+        mTestSatelliteSessionController.onSatelliteEnablementFailed();
+        processAllMessages();
+
+        // Satellite should move back to POWER_OFF state
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        moveSatelliteToEnablingState();
+
+        // Modem reset
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false);
+        processAllMessages();
+
+        // Satellite should move back to POWER_OFF state
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        powerOnSatelliteModem();
+    }
+
+    @Test
+    public void testDisablingSatellite() {
+        // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+        assertNotNull(mTestSatelliteSessionController);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        // IDLE -> DISABLING
+        moveToIdleState();
+        moveSatelliteToDisablingState();
+
+        // DISABLING -> POWER_OFF
+        moveToPowerOffState();
+
+        // TRANSFERRING -> DISABLING
+        moveToIdleState();
+        moveIdleToTransferringState();
+        moveSatelliteToDisablingState();
+
+        // DISABLING -> POWER_OFF
+        moveToPowerOffState();
+
+        // LISTENING -> DISABLING
+        moveToIdleState();
+        moveIdleToTransferringState();
+        moveTransferringToListeningState();
+        moveSatelliteToDisablingState();
+
+        // DISABLING -> POWER_OFF
+        moveToPowerOffState();
+    }
+
+    @Test
+    public void testDisablingSatelliteForNblot() {
+        when(mMockSatelliteController.isSatelliteAttachRequired()).thenReturn(true);
+
+        // Since satellite is supported, SatelliteSessionController should move to POWER_OFF state.
+        assertNotNull(mTestSatelliteSessionController);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        setupDatagramTransferringState(false);
+
+        // NOT_CONNECTED -> DISABLING
+        moveToNotConnectedState();
+        moveSatelliteToDisablingState();
+
+        // DISABLING -> POWER_OFF
+        moveToPowerOffState();
+
+        // CONNECTED -> DISABLING
+        moveToNotConnectedState();
+        moveNotConnectedToConnectedState();
+        moveSatelliteToDisablingState();
+
+        // DISABLING -> POWER_OFF
+        moveToPowerOffState();
     }
 
     private void setupDatagramTransferringState(boolean isTransferring) {
@@ -811,6 +991,127 @@
         when(mMockDatagramController.isPollingInIdleState()).thenReturn(isTransferring);
     }
 
+    private void powerOnSatelliteModem() {
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Power on the modem.
+        mTestSatelliteSessionController.onSatelliteEnablementStarted(true);
+        processAllMessages();
+
+        // SatelliteSessionController should move to ENABLING state
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_ENABLING_SATELLITE);
+        assertEquals(
+                STATE_ENABLING_SATELLITE, mTestSatelliteSessionController.getCurrentStateName());
+
+        // Satellite is powered on
+        mTestSatelliteModemStateCallback.clearModemStates();
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(true);
+        processAllMessages();
+    }
+
+    private void moveSatelliteToEnablingState() {
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+        mTestSatelliteModemStateCallback.clearModemStates();
+
+        // Power on the modem.
+        mTestSatelliteSessionController.onSatelliteEnablementStarted(true);
+        processAllMessages();
+
+        // SatelliteSessionController should move to ENABLING state
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_ENABLING_SATELLITE);
+        assertEquals(
+                STATE_ENABLING_SATELLITE, mTestSatelliteSessionController.getCurrentStateName());
+        mTestSatelliteModemStateCallback.clearModemStates();
+    }
+
+    private void moveToPowerOffState() {
+        mTestSatelliteSessionController.onSatelliteEnabledStateChanged(false);
+        processAllMessages();
+
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_OFF);
+        assertEquals(STATE_POWER_OFF, mTestSatelliteSessionController.getCurrentStateName());
+    }
+
+    private void moveToIdleState() {
+        powerOnSatelliteModem();
+
+        // SatelliteSessionController should move to IDLE state after the modem is powered on.
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_IDLE);
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+    }
+
+    private void moveIdleToTransferringState() {
+        assertEquals(STATE_IDLE, mTestSatelliteSessionController.getCurrentStateName());
+        // Start sending datagrams
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_SENDING, SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to TRANSFERRING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING);
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        assertTrue(mTestSatelliteSessionController.isSendingTriggeredDuringTransferringState());
+    }
+
+    private void moveTransferringToListeningState() {
+        assertEquals(STATE_TRANSFERRING, mTestSatelliteSessionController.getCurrentStateName());
+        // Sending datagrams is successful and done.
+        mTestSatelliteSessionController.onDatagramTransferStateChanged(
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE,
+                SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE);
+        processAllMessages();
+
+        // SatelliteSessionController should move to LISTENING state.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_LISTENING);
+        assertEquals(STATE_LISTENING, mTestSatelliteSessionController.getCurrentStateName());
+    }
+
+    private void moveToNotConnectedState() {
+        powerOnSatelliteModem();
+        // SatelliteSessionController should move to NOT_CONNECTED state after the satellite modem
+        // is powered on.
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        assertEquals(STATE_NOT_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        assertFalse(mTestSatelliteSessionController.isNbIotInactivityTimerStarted());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_NOT_CONNECTED);
+        clearInvocations(mMockDatagramController);
+    }
+
+    private void moveNotConnectedToConnectedState() {
+        // Satellite modem is connected to a satellite network.
+        mTestSatelliteSessionController.onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        processAllMessages();
+
+        // SatelliteSessionController should move to CONNECTED state
+        assertSuccessfulModemStateChangedCallback(
+                mTestSatelliteModemStateCallback, SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        assertEquals(STATE_CONNECTED, mTestSatelliteSessionController.getCurrentStateName());
+        verify(mMockDatagramController).onSatelliteModemStateChanged(
+                SatelliteManager.SATELLITE_MODEM_STATE_CONNECTED);
+        clearInvocations(mMockDatagramController);
+    }
+
+    private void moveSatelliteToDisablingState() {
+        mTestSatelliteSessionController.onSatelliteEnablementStarted(false);
+        processAllMessages();
+
+        // SatelliteSessionController should move to DISABLING state
+        assertSuccessfulModemStateChangedCallback(mTestSatelliteModemStateCallback,
+                SatelliteManager.SATELLITE_MODEM_STATE_DISABLING_SATELLITE);
+        assertEquals(
+                STATE_DISABLING_SATELLITE, mTestSatelliteSessionController.getCurrentStateName());
+    }
+
     private static class TestSatelliteModemInterface extends SatelliteModemInterface {
         private final AtomicInteger mListeningEnabledCount = new AtomicInteger(0);
         private final AtomicInteger mListeningDisabledCount = new AtomicInteger(0);
@@ -818,8 +1119,9 @@
         private int mErrorCode = SatelliteManager.SATELLITE_RESULT_SUCCESS;
 
         TestSatelliteModemInterface(@NonNull Context context,
-                SatelliteController satelliteController, @NonNull Looper looper) {
-            super(context, satelliteController, looper);
+                SatelliteController satelliteController, @NonNull Looper looper,
+                @NonNull FeatureFlags featureFlags) {
+            super(context, satelliteController, looper, featureFlags);
             mExponentialBackoff.stop();
         }
 
@@ -862,9 +1164,10 @@
     }
 
     private static class TestSatelliteSessionController extends SatelliteSessionController {
-        TestSatelliteSessionController(Context context, Looper looper, boolean isSatelliteSupported,
+        TestSatelliteSessionController(Context context, Looper looper, FeatureFlags featureFlags,
+                boolean isSatelliteSupported,
                 SatelliteModemInterface satelliteModemInterface) {
-            super(context, looper, isSatelliteSupported, satelliteModemInterface);
+            super(context, looper, featureFlags, isSatelliteSupported, satelliteModemInterface);
         }
 
         String getCurrentStateName() {
@@ -878,17 +1181,26 @@
         boolean isNbIotInactivityTimerStarted() {
             return hasMessages(EVENT_NB_IOT_INACTIVITY_TIMER_TIMED_OUT);
         }
+
+        boolean isEventDeferred(int event) {
+            return hasDeferredMessages(event);
+        }
     }
 
     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 Object mLock = new Object();
+        private final List<Integer> mModemStates = new ArrayList<>();
 
         @Override
         public void onSatelliteModemStateChanged(int state) {
             logd("onSatelliteModemStateChanged: state=" + state);
             mModemState.set(state);
+            synchronized (mLock) {
+                mModemStates.add(state);
+            }
             try {
                 mSemaphore.release();
             } catch (Exception ex) {
@@ -912,6 +1224,28 @@
         public int getModemState() {
             return mModemState.get();
         }
+
+        public int getModemState(int index) {
+            synchronized (mLock) {
+                return mModemStates.get(index);
+            }
+        }
+
+        public void clearModemStates() {
+            synchronized (mLock) {
+                mModemStates.clear();
+            }
+        }
+
+        public int getNumberOfModemStates() {
+            synchronized (mLock) {
+                return mModemStates.size();
+            }
+        }
+
+        public void clearSemaphorePermits() {
+            mSemaphore.drainPermits();
+        }
     }
 
     private static void assertSuccessfulModemStateChangedCallback(
diff --git a/tests/telephonytests/src/com/android/internal/telephony/security/CellularNetworkSecuritySafetySourceTest.java b/tests/telephonytests/src/com/android/internal/telephony/security/CellularNetworkSecuritySafetySourceTest.java
index 76118c4..e409b8d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/security/CellularNetworkSecuritySafetySourceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/security/CellularNetworkSecuritySafetySourceTest.java
@@ -36,6 +36,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.safetycenter.SafetySourceData;
+import android.safetycenter.SafetySourceIssue;
 import android.util.Singleton;
 
 import com.android.internal.R;
@@ -50,6 +51,7 @@
 import org.mockito.ArgumentCaptor;
 
 import java.time.Instant;
+import java.util.List;
 
 public final class CellularNetworkSecuritySafetySourceTest extends TelephonyTest {
 
@@ -76,13 +78,19 @@
 
         mContextFixture.putResource(R.string.scCellularNetworkSecurityTitle, "fake");
         mContextFixture.putResource(R.string.scCellularNetworkSecuritySummary, "fake");
-        mContextFixture.putResource(R.string.scNullCipherIssueNonEncryptedTitle, "fake %1$s");
-        mContextFixture.putResource(R.string.scNullCipherIssueNonEncryptedSummary, "fake");
+        mContextFixture.putResource(R.string.scCellularNetworkSecurityLearnMore,
+                "https://support.google.com/android?p=cellular_security");
+        mContextFixture.putResource(R.string.scNullCipherIssueNonEncryptedTitle, "fake");
+        mContextFixture.putResource(R.string.scNullCipherIssueNonEncryptedSummary, "fake %1$s");
+        mContextFixture.putResource(R.string.scNullCipherIssueNonEncryptedSummaryNotification,
+                "fake %1$s");
         mContextFixture.putResource(R.string.scNullCipherIssueEncryptedTitle, "fake %1$s");
-        mContextFixture.putResource(R.string.scNullCipherIssueEncryptedSummary, "fake");
+        mContextFixture.putResource(R.string.scNullCipherIssueEncryptedSummary, "fake %1$s");
         mContextFixture.putResource(R.string.scIdentifierDisclosureIssueTitle, "fake");
+        mContextFixture.putResource(R.string.scIdentifierDisclosureIssueSummaryNotification,
+                "fake %1$s %2$s");
         mContextFixture.putResource(
-                R.string.scIdentifierDisclosureIssueSummary, "fake %1$d %2$tr %3$tr %4$s");
+                R.string.scIdentifierDisclosureIssueSummary, "fake %1$s %2$s");
         mContextFixture.putResource(R.string.scNullCipherIssueActionSettings, "fake");
         mContextFixture.putResource(R.string.scNullCipherIssueActionLearnMore, "fake");
 
@@ -172,6 +180,26 @@
     }
 
     @Test
+    public void clearNullCipherState() {
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+        mSafetySource.setNullCipherState(mContext, 0, NULL_CIPHER_STATE_NOTIFY_ENCRYPTED);
+        mSafetySource.clearNullCipherState(mContext, 0);
+
+        // Once for enable, once for encrypted state, and once for clearing state
+        verify(mSafetyCenterManagerWrapper, times(3)).setSafetySourceData(data.capture());
+
+        // initial check that our encrypted state update created an issue
+        assertThat(data.getAllValues().get(1).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(1).getIssues()).hasSize(1);
+
+        // assert that our last call to clear state results in no issues sent to SC
+        assertThat(data.getAllValues().get(2).getStatus()).isNotNull();
+        assertThat(data.getAllValues().get(2).getIssues()).isEmpty();
+    }
+
+    @Test
     public void disableIdentifierDisclosueIssue_nullData() {
         // We must first enable before disabling, since a standalone call to disable may result in
         // a no-op when the default for a new notifier is to be disabled.
@@ -245,4 +273,24 @@
         assertThat(data.getAllValues().get(3).getStatus()).isNotNull();
         assertThat(data.getAllValues().get(3).getIssues()).hasSize(2);
     }
+
+    @Test
+    public void learnMoreLinkEmpty_learnMoreIsHidden() {
+        mContextFixture.putResource(R.string.scCellularNetworkSecurityLearnMore, "");
+
+        ArgumentCaptor<SafetySourceData> data = ArgumentCaptor.forClass(SafetySourceData.class);
+
+        mSafetySource.setNullCipherIssueEnabled(mContext, true);
+        mSafetySource.setNullCipherState(mContext, 0, NULL_CIPHER_STATE_NOTIFY_NON_ENCRYPTED);
+        mSafetySource.setIdentifierDisclosureIssueEnabled(mContext, true);
+        mSafetySource.setIdentifierDisclosure(mContext, 0, 12, Instant.now(), Instant.now());
+
+        verify(mSafetyCenterManagerWrapper, times(4)).setSafetySourceData(data.capture());
+        List<SafetySourceIssue.Action> actions = data.getAllValues().get(
+                3).getIssues().getFirst().getActions();
+
+        // we only see the action that takes you to the settings page
+        assertThat(actions).hasSize(1);
+        assertThat(actions.getFirst().getId()).isEqualTo("cellular_security_settings");
+    }
 }
\ No newline at end of file
diff --git a/tests/telephonytests/src/com/android/internal/telephony/security/NullCipherNotifierTest.java b/tests/telephonytests/src/com/android/internal/telephony/security/NullCipherNotifierTest.java
index 0bb7b76..6a4d2fb 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/security/NullCipherNotifierTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/security/NullCipherNotifierTest.java
@@ -45,6 +45,7 @@
 public class NullCipherNotifierTest {
 
     private static final int SUB_ID = 3425;
+    private static final int PHONE_ID = 999;
     private static final List<Integer> NON_TRANSPORT_LAYER_EVENTS =
             List.of(SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP,
                     SecurityAlgorithmUpdate.CONNECTION_EVENT_VOLTE_SIP_SOS,
@@ -149,9 +150,11 @@
     @Test
     public void onSecurityAlgorithmUpdate_enabled_unprotectedEmergency_noSafetySourceUpdate() {
         NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
 
         notifier.onSecurityAlgorithmUpdate(
                 mContext,
+                PHONE_ID,
                 SUB_ID,
                 new SecurityAlgorithmUpdate(
                         SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G,
@@ -163,6 +166,24 @@
     }
 
     @Test
+    public void onSecurityAlgorithmUpdate_disabled_noSafetySourceUpdate() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.disable(mContext);
+
+        notifier.onSecurityAlgorithmUpdate(
+                mContext,
+                PHONE_ID,
+                SUB_ID,
+                new SecurityAlgorithmUpdate(
+                        SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G,
+                        SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                        SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                        /* isUnprotectedEmergency= */ false));
+
+        verify(mSafetySource, never()).setNullCipherState(any(), anyInt(), anyInt());
+    }
+
+    @Test
     public void onSecurityAlgorithmUpdate_enabled_nonTransportLayerEvent_noSafetySourceUpdate() {
         NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
 
@@ -170,6 +191,7 @@
             clearInvocations(mSafetySource);
             notifier.onSecurityAlgorithmUpdate(
                     mContext,
+                    PHONE_ID,
                     SUB_ID,
                     new SecurityAlgorithmUpdate(
                             connectionEvent,
@@ -185,12 +207,14 @@
     @Test
     public void onUpdate_enabled_transportLayerEvent_encryptionNullCipher_notifyNonEncrypted() {
         NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
 
         for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
             for (int encryptionAlgorithm : NULL_CIPHERS) {
                 clearInvocations(mSafetySource);
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -214,12 +238,14 @@
     @Test
     public void onUpdate_enabled_transportLayerEvent_integrityNullCipher_notifyNonEncrypted() {
         NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
 
         for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
             for (int integrityAlgorithm : NULL_CIPHERS) {
                 clearInvocations(mSafetySource);
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -243,12 +269,14 @@
     @Test
     public void onUpdate_enabled_transportLayerEvent_encryptionNonNullCipher_encrypted() {
         NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
 
         for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
             for (int encryptionAlgorithm : NON_NULL_CIPHERS) {
                 clearInvocations(mSafetySource);
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -272,12 +300,14 @@
     @Test
     public void onUpdate_enabled_transportLayerEvent_integrityNonNullCipher_encrypted() {
         NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
 
         for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
             for (int integrityAlgorithm : NON_NULL_CIPHERS) {
                 clearInvocations(mSafetySource);
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -301,11 +331,13 @@
     @Test
     public void onUpdate_enabled_transportLayerEvent_encryptionNonNullCipher_notifyEncrypted() {
         NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
 
         for (int connectionEvent : TRANSPORT_LAYER_EVENTS) {
             for (int encryptionAlgorithm : NON_NULL_CIPHERS) {
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -316,6 +348,7 @@
                 clearInvocations(mSafetySource);
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -345,6 +378,7 @@
             for (int integrityAlgorithm : NON_NULL_CIPHERS) {
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -355,6 +389,7 @@
                 clearInvocations(mSafetySource);
                 notifier.onSecurityAlgorithmUpdate(
                         mContext,
+                        PHONE_ID,
                         SUB_ID,
                         new SecurityAlgorithmUpdate(
                                 connectionEvent,
@@ -374,4 +409,88 @@
             }
         }
     }
+
+    @Test
+    public void onSecurityAlgorithmUpdate_updateSubscriptionId_clearsOldId() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
+
+        int connectionEvent = SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G;
+        int encryptionAlgorithm = SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2;
+        int subId2 = 1337;
+
+        notifier.onSecurityAlgorithmUpdate(
+                mContext,
+                PHONE_ID,
+                SUB_ID,
+                getWellEncryptedUpdate());
+
+        notifier.setSubscriptionMapping(mContext, PHONE_ID, subId2);
+
+        verify(
+                mSafetySource,
+                times(1).description(
+                        "Connection event: " + connectionEvent
+                                + " Encryption algorithm: " + encryptionAlgorithm))
+                .setNullCipherState(
+                        eq(mContext),
+                        eq(SUB_ID),
+                        eq(NULL_CIPHER_STATE_ENCRYPTED));
+
+        verify(mSafetySource, times(1)).clearNullCipherState(eq(mContext), eq(SUB_ID));
+    }
+
+    @Test
+    public void onSecurityAlgorithmUpdate_newSubIdOnAlgoUpdate_clearsOldId() {
+        NullCipherNotifier notifier = new NullCipherNotifier(mExecutor, mSafetySource);
+        notifier.enable(mContext);
+
+        int connectionEvent = SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G;
+        int encryptionAlgorithm = SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2;
+        int subId2 = 1337;
+
+        notifier.onSecurityAlgorithmUpdate(
+                mContext,
+                PHONE_ID,
+                SUB_ID,
+                getWellEncryptedUpdate());
+
+        notifier.onSecurityAlgorithmUpdate(
+                mContext,
+                PHONE_ID,
+                subId2,
+                getWellEncryptedUpdate());
+
+        verify(
+                mSafetySource,
+                times(1).description(
+                        "SubId: " + SUB_ID + "Connection event: " + connectionEvent
+                                + " Encryption algorithm: " + encryptionAlgorithm))
+                .setNullCipherState(
+                        eq(mContext),
+                        eq(SUB_ID),
+                        eq(NULL_CIPHER_STATE_ENCRYPTED));
+
+        // The update with subId2 should clear subId1 data since they have the same phone id
+        verify(mSafetySource, times(1)).clearNullCipherState(eq(mContext), eq(SUB_ID));
+
+        verify(
+                mSafetySource,
+                times(1).description(
+                        "SubId: " + SUB_ID + "Connection event: " + connectionEvent
+                                + " Encryption algorithm: " + encryptionAlgorithm))
+                .setNullCipherState(
+                        eq(mContext),
+                        eq(subId2),
+                        eq(NULL_CIPHER_STATE_ENCRYPTED));
+
+    }
+
+    private SecurityAlgorithmUpdate getWellEncryptedUpdate() {
+        return new SecurityAlgorithmUpdate(
+                SecurityAlgorithmUpdate.CONNECTION_EVENT_AS_SIGNALLING_5G,
+                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_EEA2,
+                SecurityAlgorithmUpdate.SECURITY_ALGORITHM_HMAC_SHA1_96,
+                /* isUnprotectedEmergency= */ false);
+    }
 }
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 7339e42..62b9def 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionDatabaseManagerTest.java
@@ -76,8 +76,8 @@
     static final String FAKE_DEFAULT_CARD_NAME = "CARD %d";
     static final String FAKE_ICCID1 = "123456";
     static final String FAKE_ICCID2 = "456789";
-    static final String FAKE_PHONE_NUMBER1 = "6502530000";
-    static final String FAKE_PHONE_NUMBER2 = "4089961010";
+    static final String FAKE_PHONE_NUMBER1 = "9995551234";
+    static final String FAKE_PHONE_NUMBER2 = "9998887777";
     static final String FAKE_CARRIER_NAME1 = "A-Mobile";
     static final String FAKE_CARRIER_NAME2 = "B-Mobile";
     static final int FAKE_COLOR1 = 1;
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 6e2c5bf..de43b85 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionInfoInternalTest.java
@@ -341,4 +341,69 @@
         assertThat(subInfoNull.getCountryIso()).isEqualTo("");
         assertThat(subInfoNull.getGroupOwner()).isEqualTo("");
     }
+
+    @Test
+    public void testIsVisible() {
+        // Regular profile
+        SubscriptionInfoInternal regularSub =
+                new SubscriptionInfoInternal.Builder()
+                    .setId(2)
+                    .setIccId(SubscriptionDatabaseManagerTest.FAKE_ICCID1)
+                    .setSimSlotIndex(0)
+                    .setProfileClass(SubscriptionManager.PROFILE_CLASS_OPERATIONAL)
+                    .setOnlyNonTerrestrialNetwork(0)
+                    .setOpportunistic(0)
+                    .setGroupUuid(SubscriptionDatabaseManagerTest.FAKE_UUID1)
+                    .build();
+        assertThat(regularSub.isVisible()).isTrue();
+
+        // Provisioning profile
+        SubscriptionInfoInternal provSub =
+                new SubscriptionInfoInternal.Builder()
+                    .setId(2)
+                    .setIccId(SubscriptionDatabaseManagerTest.FAKE_ICCID1)
+                    .setSimSlotIndex(0)
+                    .setProfileClass(SubscriptionManager.PROFILE_CLASS_PROVISIONING)
+                    .setOnlyNonTerrestrialNetwork(0)
+                    .setOpportunistic(0)
+                    .build();
+        assertThat(provSub.isVisible()).isFalse();
+
+        // NTN profile
+        SubscriptionInfoInternal ntnSub =
+                new SubscriptionInfoInternal.Builder()
+                    .setId(2)
+                    .setIccId(SubscriptionDatabaseManagerTest.FAKE_ICCID1)
+                    .setSimSlotIndex(0)
+                    .setOnlyNonTerrestrialNetwork(1)
+                    .setProfileClass(SubscriptionManager.PROFILE_CLASS_OPERATIONAL)
+                    .setOpportunistic(0)
+                    .build();
+        assertThat(ntnSub.isVisible()).isFalse();
+
+        // Opportunistic profile without group UUID
+        SubscriptionInfoInternal opportunisticSub =
+                new SubscriptionInfoInternal.Builder()
+                    .setId(2)
+                    .setIccId(SubscriptionDatabaseManagerTest.FAKE_ICCID1)
+                    .setSimSlotIndex(0)
+                    .setOnlyNonTerrestrialNetwork(0)
+                    .setProfileClass(SubscriptionManager.PROFILE_CLASS_OPERATIONAL)
+                    .setOpportunistic(1)
+                    .build();
+        assertThat(opportunisticSub.isVisible()).isTrue();
+
+        // Opportunistic profile with group UUID
+        SubscriptionInfoInternal opportunisticSubUuid =
+                new SubscriptionInfoInternal.Builder()
+                    .setId(2)
+                    .setIccId(SubscriptionDatabaseManagerTest.FAKE_ICCID1)
+                    .setSimSlotIndex(0)
+                    .setOnlyNonTerrestrialNetwork(0)
+                    .setProfileClass(SubscriptionManager.PROFILE_CLASS_OPERATIONAL)
+                    .setOpportunistic(1)
+                    .setGroupUuid(SubscriptionDatabaseManagerTest.FAKE_UUID1)
+                    .build();
+        assertThat(opportunisticSubUuid.isVisible()).isFalse();
+    }
 }
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 cc20dfd..eb06ff1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
@@ -43,13 +43,17 @@
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_PHONE_NUMBER2;
 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_SUBSCRIPTION_INFO1;
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_SUBSCRIPTION_INFO2;
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_UUID1;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
 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.anyInt;
@@ -131,11 +135,13 @@
 import java.io.StringWriter;
 import java.lang.reflect.Array;
 import java.lang.reflect.Field;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -238,7 +244,6 @@
         doReturn(false).when(mFlags).enforceTelephonyFeatureMappingForPublicApis();
         doReturn(true).when(mPackageManager).hasSystemFeature(
                 eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
-
         logd("SubscriptionManagerServiceTest -Setup!");
     }
 
@@ -421,21 +426,35 @@
     @Test
     public void testSetAdminOwned() {
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
-        mSubscriptionManagerServiceUT.addSubInfo(FAKE_ICCID1, FAKE_CARRIER_NAME1,
-                0, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
+        mSubscriptionManagerServiceUT.addSubInfo(FAKE_ICCID1, FAKE_CARRIER_NAME1, 0,
+                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
         processAllMessages();
         String groupOwner = "test";
 
         mSubscriptionManagerServiceUT.setGroupOwner(1, groupOwner);
 
-        SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT
-                .getSubscriptionInfoInternal(1);
+        SubscriptionInfoInternal subInfo =
+                mSubscriptionManagerServiceUT.getSubscriptionInfoInternal(1);
         assertThat(subInfo).isNotNull();
         assertThat(subInfo.getGroupOwner()).isEqualTo(groupOwner);
         verify(mMockedSubscriptionManagerServiceCallback).onSubscriptionChanged(eq(1));
     }
 
     @Test
+    public void testSetGroupOwner_callerMissingpPermission_throws() {
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+        mSubscriptionManagerServiceUT.addSubInfo(FAKE_ICCID1, FAKE_CARRIER_NAME1, 0,
+                SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
+        processAllMessages();
+        String groupOwner = "test";
+        // Remove MODIFY_PHONE_STATE
+        mContextFixture.removeCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+
+        assertThrows(SecurityException.class,
+                () -> mSubscriptionManagerServiceUT.setGroupOwner(1, groupOwner));
+    }
+
+    @Test
     @DisableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
     public void testSetPhoneNumber() {
         doReturn(false).when(mFlags).enforceTelephonyFeatureMapping();
@@ -1789,6 +1808,45 @@
     }
 
     @Test
+    public void testGetPhoneNumberSourcePriority() throws Exception {
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PHONE_NUMBERS);
+
+        String phoneNumberFromCarrier = "8675309";
+        String phoneNumberFromUicc = "1112223333";
+        String phoneNumberFromIms = "5553466";
+        String phoneNumberFromPhoneObject = "8001234567";
+
+        doReturn(phoneNumberFromPhoneObject).when(mPhone).getLine1Number();
+
+        SubscriptionInfoInternal multiNumberSubInfo =
+                new SubscriptionInfoInternal.Builder(FAKE_SUBSCRIPTION_INFO1)
+                        .setNumberFromCarrier(phoneNumberFromCarrier)
+                        .setNumber(phoneNumberFromUicc)
+                        .setNumberFromIms(phoneNumberFromIms)
+                        .build();
+        int subId = insertSubscription(multiNumberSubInfo);
+
+        assertThat(mSubscriptionManagerServiceUT.getPhoneNumberFromFirstAvailableSource(
+                subId, CALLING_PACKAGE, CALLING_FEATURE)).isEqualTo(phoneNumberFromCarrier);
+
+        multiNumberSubInfo =
+                new SubscriptionInfoInternal.Builder(multiNumberSubInfo)
+                        .setNumberFromCarrier("")
+                        .setNumber(phoneNumberFromUicc)
+                        .setNumberFromIms(phoneNumberFromIms)
+                        .build();
+        subId = insertSubscription(multiNumberSubInfo);
+
+        assertThat(mSubscriptionManagerServiceUT.getPhoneNumberFromFirstAvailableSource(
+                subId, CALLING_PACKAGE, CALLING_FEATURE)).isEqualTo(phoneNumberFromPhoneObject);
+
+        doReturn("").when(mPhone).getLine1Number();
+
+        assertThat(mSubscriptionManagerServiceUT.getPhoneNumberFromFirstAvailableSource(
+                subId, CALLING_PACKAGE, CALLING_FEATURE)).isEqualTo(phoneNumberFromIms);
+    }
+
+    @Test
     public void testSetUiccApplicationsEnabled() {
         insertSubscription(FAKE_SUBSCRIPTION_INFO1);
 
@@ -2484,6 +2542,39 @@
     }
 
     @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);
+
+        mSubscriptionManagerServiceUT.setDefaultVoiceSubId(subId);
+
+        assertThat(
+                mSubscriptionManagerServiceUT.getPhoneNumberFromFirstAvailableSource(
+                        subId, CALLING_PACKAGE, CALLING_FEATURE)).isEqualTo(FAKE_PHONE_NUMBER1);
+        assertThat(
+                mSubscriptionManagerServiceUT.getPhoneNumber(
+                        SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                        SubscriptionManager.PHONE_NUMBER_SOURCE_UICC,
+                        CALLING_PACKAGE,
+                        CALLING_FEATURE)).isEqualTo(FAKE_PHONE_NUMBER1);
+        assertThat(
+                mSubscriptionManagerServiceUT.getPhoneNumber(
+                        SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                        SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER,
+                        CALLING_PACKAGE,
+                        CALLING_FEATURE)).isEqualTo(FAKE_PHONE_NUMBER1);
+        assertThat(
+                mSubscriptionManagerServiceUT.getPhoneNumber(
+                        SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+                        SubscriptionManager.PHONE_NUMBER_SOURCE_IMS,
+                        CALLING_PACKAGE,
+                        CALLING_FEATURE)).isEqualTo(FAKE_PHONE_NUMBER1);
+    }
+
+    @Test
     public void testEsimActivation() {
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
@@ -3135,6 +3226,69 @@
     }
 
     @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)
+                .setNickname(FAKE_CARRIER_NAME1)
+                .setServiceProviderName(FAKE_CARRIER_NAME1)
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_OPERATIONAL)
+                .setCarrierIdentifier(new CarrierIdentifier(FAKE_MCC1, FAKE_MNC1,
+                        FAKE_CARRIER_NAME1, null, null, null, FAKE_CARRIER_ID1, FAKE_CARRIER_ID1))
+                .setUiccAccessRule(Arrays.asList(UiccAccessRule.decodeRules(
+                        FAKE_NATIVE_ACCESS_RULES1)))
+                .build();
+
+        GetEuiccProfileInfoListResult result = new GetEuiccProfileInfoListResult(
+                EuiccService.RESULT_OK, new EuiccProfileInfo[]{profileInfo1}, false);
+        doReturn(result).when(mEuiccController).blockingGetEuiccProfileInfoList(eq(1));
+        doReturn(TelephonyManager.INVALID_PORT_INDEX).when(mUiccSlot)
+                .getPortIndexFromIccId(anyString());
+        doReturn(FAKE_ICCID1).when(mUiccController).convertToCardString(eq(1));
+
+        mSubscriptionManagerServiceUT.updateEmbeddedSubscriptions(List.of(1), null);
+        processAllMessages();
+
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT
+                .getSubscriptionInfoInternal(1);
+        assertThat(subInfo.getOnlyNonTerrestrialNetwork())
+                .isEqualTo(FAKE_SATELLITE_IS_NTN_DISABLED);
+
+        mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
+                FAKE_CARRIER_NAME1);
+        EuiccProfileInfo profileInfo2 = new EuiccProfileInfo.Builder(FAKE_ICCID2)
+                .setIccid(FAKE_ICCID2)
+                .setNickname(FAKE_CARRIER_NAME2)
+                .setServiceProviderName("")
+                .setProfileClass(SubscriptionManager.PROFILE_CLASS_OPERATIONAL)
+                .setCarrierIdentifier(new CarrierIdentifier(FAKE_MCC2, FAKE_MNC2,
+                        FAKE_CARRIER_NAME2, null, null, null, FAKE_CARRIER_ID2, FAKE_CARRIER_ID2))
+                .setUiccAccessRule(Arrays.asList(UiccAccessRule.decodeRules(
+                        FAKE_NATIVE_ACCESS_RULES2)))
+                .build();
+        result = new GetEuiccProfileInfoListResult(
+                EuiccService.RESULT_OK, new EuiccProfileInfo[]{profileInfo2}, false);
+        doReturn(result).when(mEuiccController).blockingGetEuiccProfileInfoList(eq(2));
+        doReturn(TelephonyManager.INVALID_PORT_INDEX).when(mUiccSlot)
+                .getPortIndexFromIccId(anyString());
+        doReturn(FAKE_ICCID2).when(mUiccController).convertToCardString(eq(2));
+
+        mSubscriptionManagerServiceUT.updateEmbeddedSubscriptions(List.of(2), null);
+        processAllMessages();
+
+        subInfo = mSubscriptionManagerServiceUT
+                .getSubscriptionInfoInternal(2);
+        assertThat(subInfo.getOnlyNonTerrestrialNetwork())
+                .isEqualTo(FAKE_SATELLITE_IS_NTN_DISABLED);
+
+        System.setProperty("persist.radio.allow_mock_modem", "false");
+        doReturn(false).when(mFlags).oemEnabledSatelliteFlag();
+    }
+
+    @Test
     public void testIsSatelliteSpnWithNullCarrierIdentifier() {
         mContextFixture.putResource(R.string.config_satellite_sim_spn_identifier,
                 FAKE_CARRIER_NAME1);
@@ -3208,4 +3362,60 @@
         System.setProperty("persist.radio.allow_mock_modem", "false");
         doReturn(false).when(mFlags).oemEnabledSatelliteFlag();
     }
+
+    @Test
+    public void testGetSatelliteEntitlementPlmnList() throws Exception {
+        mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
+
+        // When the empty list is stored, verify whether SubscriptionInfoInternal returns an
+        // empty string and SubscriptionManagerService returns an empty List.
+        insertSubscription(FAKE_SUBSCRIPTION_INFO1);
+        List<String> expectedPlmnList = new ArrayList<>();
+        int subId = 1;
+
+        SubscriptionInfoInternal subInfo = mSubscriptionManagerServiceUT
+                .getSubscriptionInfoInternal(subId);
+        assertTrue(subInfo.getSatelliteEntitlementPlmns().isEmpty());
+        assertEquals(expectedPlmnList,
+                mSubscriptionManagerServiceUT.getSatelliteEntitlementPlmnList(subId));
+
+        // When the list is stored as [123123,12310], verify whether SubscriptionInfoInternal
+        // returns the string as "123123,12310" and SubscriptionManagerService returns the List as
+        // [123123,12310].
+        insertSubscription(FAKE_SUBSCRIPTION_INFO2);
+        String expectedPlmn = FAKE_SATELLITE_ENTITLEMENT_PLMNS1;
+        expectedPlmnList = Arrays.stream(expectedPlmn.split(",")).collect(Collectors.toList());
+        subId = 2;
+
+        subInfo = mSubscriptionManagerServiceUT.getSubscriptionInfoInternal(subId);
+        assertEquals(expectedPlmn, subInfo.getSatelliteEntitlementPlmns());
+        assertEquals(expectedPlmnList,
+                mSubscriptionManagerServiceUT.getSatelliteEntitlementPlmnList(subId));
+
+        // When calling SubscriptionDatabaseManager#getSubscriptionInfoInternalreturns returns a
+        // null, then verify the SubscriptionManagerService returns an empty List.
+        SubscriptionDatabaseManager mockSubscriptionDatabaseManager = Mockito.mock(
+                SubscriptionDatabaseManager.class);
+        Field field = SubscriptionManagerService.class.getDeclaredField(
+                "mSubscriptionDatabaseManager");
+        field.setAccessible(true);
+        field.set(mSubscriptionManagerServiceUT, mockSubscriptionDatabaseManager);
+
+        doReturn(null).when(mockSubscriptionDatabaseManager).getSubscriptionInfoInternal(anyInt());
+        expectedPlmnList = new ArrayList<>();
+        assertEquals(expectedPlmnList,
+                mSubscriptionManagerServiceUT.getSatelliteEntitlementPlmnList(subId));
+
+        // When calling SubscriptionDatabaseManager#getSubscriptionInfoInternalreturns returns a
+        // non null. And when calling SubscriptionInfoInternal#getSatelliteEntitlementPlmns
+        // returns a null, then verify the SubscriptionManagerService returns an empty List.
+        SubscriptionInfoInternal mockSubscriptionInfoInternal = Mockito.mock(
+                SubscriptionInfoInternal.class);
+        doReturn(mockSubscriptionInfoInternal).when(
+                mockSubscriptionDatabaseManager).getSubscriptionInfoInternal(anyInt());
+        doReturn(null).when(mockSubscriptionInfoInternal).getSatelliteEntitlementPlmns();
+
+        assertEquals(expectedPlmnList,
+                mSubscriptionManagerServiceUT.getSatelliteEntitlementPlmnList(subId));
+    }
 }
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 1a846c4..5c1993f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/uicc/UiccPortTest.java
@@ -20,11 +20,16 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 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;
@@ -33,9 +38,11 @@
 
 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;
 
@@ -54,9 +61,13 @@
 
     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 */
@@ -144,6 +155,20 @@
         verify(mUiccProfile).iccCloseLogicalChannel(eq(CHANNEL_ID), eq(false), eq(null));
     }
 
+    @Test
+    @SmallTest
+    public void testOnOpenLogicalChannel_withPortDisposed_noRecordLeft() {
+        IccLogicalChannelRequest request = getIccLogicalChannelRequest();
+
+        mUiccPort.onLogicalChannelOpened(request);
+        mUiccPort.dispose();
+
+        UiccPort.OpenLogicalChannelRecord record = mUiccPort.getOpenLogicalChannelRecord(
+                CHANNEL_ID);
+        assertThat(record).isNull();
+        verify(mUiccProfile, never()).iccCloseLogicalChannel(anyInt(), anyBoolean(), any());
+    }
+
     private IccLogicalChannelRequest getIccLogicalChannelRequest() {
         IccLogicalChannelRequest request = new IccLogicalChannelRequest();
         request.channel = CHANNEL_ID;
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 b073c6a..cf3f900 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
@@ -37,6 +37,7 @@
 import com.android.internal.telephony.CommandsInterface;
 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;
@@ -93,7 +94,8 @@
         mResponseCaptor = new ResponseCaptor();
         mSelectResponse = null;
 
-        mSender = new ApduSender(mMockCi, AID, false /* supportExtendedApdu */);
+        mSender = new ApduSender(InstrumentationRegistry.getContext(), 0 /* phoneId= */,
+                            mMockCi, AID, false /* supportExtendedApdu */);
         mLooper = TestableLooper.get(this);
     }