[automerger skipped] Adjusted the timing of getting instance of the SatelliteController. am: 59d41454e0 -s ours

am skip reason: Merged-In Ib35cc76e44ec494e753cdff2e1b2afa4a84bb634 with SHA-1 ab1deb9351 is already in history

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

Change-Id: I0b8086821a16fe49cb4ac7c84dd4526072fe5300
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 8f363b6..1885032 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -21,6 +21,7 @@
 aconfig_declarations {
     name: "telephony_flags",
     package: "com.android.internal.telephony.flags",
+    container: "system",
     srcs: [
         "calling.aconfig",
         "data.aconfig",
@@ -32,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 e67ebc6..c1dc7e7 100644
--- a/flags/calling.aconfig
+++ b/flags/calling.aconfig
@@ -1,15 +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 6334803..f93999b 100644
--- a/flags/data.aconfig
+++ b/flags/data.aconfig
@@ -1,5 +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"
@@ -10,6 +23,7 @@
   }
 }
 
+# OWNER=linggm TARGET=24Q3
 flag {
   name: "auto_data_switch_rat_ss"
   namespace: "telephony"
@@ -17,6 +31,7 @@
   bug:"260928808"
 }
 
+# OWNER=linggm TARGET=24Q2
 flag {
   name: "use_alarm_callback"
   namespace: "telephony"
@@ -24,6 +39,7 @@
   bug: "311476875"
 }
 
+# OWNER=linggm TARGET=24Q2
 flag {
   name: "refine_preferred_data_profile_selection"
   namespace: "telephony"
@@ -31,6 +47,7 @@
   bug: "311476883"
 }
 
+# OWNER=linggm TARGET=24Q2
 flag {
   name: "unthrottle_check_transport"
   namespace: "telephony"
@@ -38,6 +55,7 @@
   bug: "303922311"
 }
 
+# OWNER=linggm TARGET=24Q1
 flag {
   name: "relax_ho_teardown"
   namespace: "telephony"
@@ -45,6 +63,7 @@
   bug: "270895912"
 }
 
+# OWNER=linggm TARGET=24Q2
 flag {
   name: "allow_mmtel_in_non_vops"
   namespace: "telephony"
@@ -52,6 +71,7 @@
   bug: "241198464"
 }
 
+# OWNER=jackyu TARGET=24Q2
 flag {
   name: "metered_embb_urlcc"
   namespace: "telephony"
@@ -59,27 +79,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"
@@ -87,6 +114,7 @@
   bug: "309896936"
 }
 
+# OWNER=qingqi TARGET=24Q3
 flag {
   name: "vonr_enabled_metric"
   namespace: "telephony"
@@ -94,6 +122,7 @@
   bug:"288449751"
 }
 
+# OWNER=willycwhu TARGET=24Q2
 flag {
   name: "ignore_existing_networks_for_internet_allowed_checking"
   namespace: "telephony"
@@ -101,6 +130,7 @@
   bug: "284420611"
 }
 
+# OWNER=apsankar TARGET=24Q3
 flag {
   name: "data_call_session_stats_captures_cross_sim_calling"
   namespace: "telephony"
@@ -108,6 +138,7 @@
   bug: "313956117"
 }
 
+# OWNER=jackyu TARGET=24Q2
 flag {
   name: "force_iwlan_mms"
   namespace: "telephony"
@@ -115,6 +146,7 @@
   bug: "316211526"
 }
 
+# OWNER=sewook TARGET=24Q3
 flag {
   name: "reconnect_qualified_network"
   namespace: "telephony"
@@ -122,9 +154,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 2e1dfc8..623c3b6 100644
--- a/flags/domainselection.aconfig
+++ b/flags/domainselection.aconfig
@@ -1,5 +1,7 @@
 package: "com.android.internal.telephony.flags"
+container: "system"
 
+# OWNER=forestchoi TARGET=24Q3
 flag {
     name: "ap_domain_selection_enabled"
     namespace: "telephony"
@@ -7,6 +9,7 @@
     bug:"258112541"
 }
 
+# OWNER=forestchoi TARGET=24Q3
 flag {
     name: "use_aosp_domain_selection_service"
     namespace: "telephony"
@@ -14,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 d09259e..4eff505 100644
--- a/flags/ims.aconfig
+++ b/flags/ims.aconfig
@@ -1,5 +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"
@@ -7,6 +9,7 @@
     bug:"288002989"
 }
 
+# OWNER=joonhunshin TARGET=24Q2
 flag {
     name: "ignore_already_terminated_incoming_call_before_registering_listener"
     namespace: "telephony"
@@ -14,6 +17,7 @@
     bug:"289461637"
 }
 
+# OWNER=joonhunshin TARGET=24Q2
 flag {
     name: "clear_cached_ims_phone_number_when_device_lost_ims_registration"
     namespace: "telephony"
@@ -21,6 +25,7 @@
     bug:"288002989"
 }
 
+# OWNER=sangyun TARGET=24Q2
 flag {
     name: "update_ims_service_by_gathering_provisioning_changes"
     namespace: "telephony"
@@ -28,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"
@@ -42,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"
@@ -56,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 0dc9f8d..f16ee38 100644
--- a/flags/iwlan.aconfig
+++ b/flags/iwlan.aconfig
@@ -1,13 +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 1ba89ba..1030ba7 100644
--- a/flags/messaging.aconfig
+++ b/flags/messaging.aconfig
@@ -1,12 +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"
@@ -14,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 1e714c5..9d5cbd6 100644
--- a/flags/misc.aconfig
+++ b/flags/misc.aconfig
@@ -1,5 +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"
@@ -8,6 +21,7 @@
   is_fixed_read_only: true
 }
 
+# OWNER=tnd TARGET=24Q1
 flag {
   name: "log_mms_sms_database_access_info"
   namespace: "telephony"
@@ -15,6 +29,7 @@
   bug: "275225402"
 }
 
+# OWNER=tjstuart TARGET=24Q3
 flag {
   name: "stop_spamming_emergency_notification"
   namespace: "telephony"
@@ -22,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"
@@ -36,6 +54,7 @@
   bug: "309655251"
 }
 
+# OWNER=sangyun TARGET=24Q2
 flag {
   name: "reorganize_roaming_notification"
   namespace: "telephony"
@@ -43,6 +62,7 @@
   bug: "310594087"
 }
 
+# OWNER=sangyun TARGET=24Q2
 flag {
   name: "dismiss_network_selection_notification_on_sim_disable"
   namespace: "telephony"
@@ -50,6 +70,7 @@
   bug: "310594186"
 }
 
+# OWNER=nagendranb TARGET=24Q3
 flag {
   name: "enable_telephony_analytics"
   namespace: "telephony"
@@ -57,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"
@@ -81,6 +107,7 @@
     }
 }
 
+# OWNER=rambowang TARGET=24Q3
 flag {
     name: "add_anomaly_when_notify_config_changed_with_invalid_phone"
     namespace: "telephony"
@@ -91,6 +118,7 @@
     }
 }
 
+# OWNER=rambowang TARGET=24Q3
 flag {
     name: "hide_preinstalled_carrier_app_at_most_once"
     namespace: "telephony"
@@ -100,3 +128,92 @@
         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 ab917f0..7c09ba3 100644
--- a/flags/network.aconfig
+++ b/flags/network.aconfig
@@ -1,5 +1,7 @@
 package: "com.android.internal.telephony.flags"
+container: "system"
 
+# OWNER=nharold TARGET=24Q1
 flag {
     name: "enable_carrier_config_n1_control_attempt2"
     namespace: "telephony"
@@ -11,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"
@@ -32,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"
@@ -46,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"
@@ -60,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 798ce40..27ab0b1 100644
--- a/flags/satellite.aconfig
+++ b/flags/satellite.aconfig
@@ -1,19 +1,25 @@
 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"
diff --git a/flags/subscription.aconfig b/flags/subscription.aconfig
index cebedd5..32e8f2d 100644
--- a/flags/subscription.aconfig
+++ b/flags/subscription.aconfig
@@ -1,26 +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"
@@ -28,16 +36,32 @@
   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"
-}
\ No newline at end of file
+}
+
+# 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
+  }
+}
diff --git a/flags/telephony.aconfig b/flags/telephony.aconfig
deleted file mode 100644
index 9ef70b1..0000000
--- a/flags/telephony.aconfig
+++ /dev/null
@@ -1,43 +0,0 @@
-package: "com.android.internal.telephony.flags"
-
-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 c1b860f..2d7b643 100644
--- a/flags/uicc.aconfig
+++ b/flags/uicc.aconfig
@@ -1,32 +1,52 @@
 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=24Q3
 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"
+}
diff --git a/proto/src/persist_atoms.proto b/proto/src/persist_atoms.proto
index 46a2400..6a3dcbd 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: 80
 message PersistAtoms {
     /* Aggregated RAT usage during the call. */
     repeated VoiceCallRatUsage voice_call_rat_usage = 1;
@@ -231,6 +231,36 @@
 
     /* 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;
 }
 
 // The canonical versions of the following enums live in:
@@ -375,6 +405,7 @@
     optional bool is_non_dds = 22;
     optional bool is_iwlan_cross_sim = 23;
     optional bool is_ntn = 24;
+    optional bool is_satellite_transport = 25;
 }
 
 message CellularServiceState {
@@ -464,6 +495,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;
@@ -716,3 +753,57 @@
     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;
+}
diff --git a/proto/src/telephony.proto b/proto/src/telephony.proto
index b87728b..92f62cc 100644
--- a/proto/src/telephony.proto
+++ b/proto/src/telephony.proto
@@ -259,8 +259,8 @@
   // Roaming type
   enum RoamingType {
 
-    // Unknown. The default value. Different from ROAMING_TYPE_UNKNOWN.
-    UNKNOWN = -1;
+    // Undefined. The default value. Different from ROAMING_TYPE_UNKNOWN.
+    ROAMING_TYPE_UNDEFINED = -1;
 
     // In home network
     ROAMING_TYPE_NOT_ROAMING = 0;
@@ -346,10 +346,10 @@
   optional TelephonyOperator data_operator = 2;
 
   // Current voice network roaming type
-  optional RoamingType voice_roaming_type = 3 [default = UNKNOWN];
+  optional RoamingType voice_roaming_type = 3 [default = ROAMING_TYPE_UNDEFINED];
 
   // Current data network roaming type
-  optional RoamingType data_roaming_type = 4 [default = UNKNOWN];
+  optional RoamingType data_roaming_type = 4 [default = ROAMING_TYPE_UNDEFINED];
 
   // Current voice radio technology
   optional RadioAccessTechnology voice_rat = 5 [default = UNKNOWN];
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/GsmCdmaPhone.java b/src/java/com/android/internal/telephony/GsmCdmaPhone.java
index db0fb2d..c4d9a17 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);
 
@@ -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();
     }
@@ -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;
 
@@ -5444,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/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index 61cebbf..83d58af 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -18,6 +18,7 @@
 
 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;
@@ -159,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 {
@@ -244,6 +250,8 @@
         ccm.registerCarrierConfigChangeListener(this::post,
                 (slotIndex, subId, carrierId, specificCarrierId) ->
                         onCarrierConfigChanged(slotIndex, subId));
+
+        mConvertedPsimSubId = getConvertedPsimSubscriptionId();
     }
 
     private boolean hasCalling() {
@@ -404,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);
             }
@@ -445,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();
         }
     }
 
@@ -608,7 +618,6 @@
      */
     protected void updateDefaults() {
         if (DBG) log("updateDefaults");
-
         if (!isReadyToReevaluate()) return;
 
         List<SubscriptionInfo> activeSubInfos = mSubscriptionManagerService
@@ -687,7 +696,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);
         }
@@ -803,6 +817,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 bf973e1..324e881 100644
--- a/src/java/com/android/internal/telephony/Phone.java
+++ b/src/java/com/android/internal/telephony/Phone.java
@@ -779,6 +779,7 @@
             case EVENT_SET_NETWORK_MANUAL_COMPLETE:
             case EVENT_SET_NETWORK_AUTOMATIC_COMPLETE:
                 handleSetSelectNetwork((AsyncResult) msg.obj);
+                onSetNetworkSelectionModeCompleted();
                 return;
         }
 
@@ -1536,6 +1537,12 @@
     }
 
     /**
+     * Called when setting network selection mode is complete.
+     */
+    protected void onSetNetworkSelectionModeCompleted() {
+    }
+
+    /**
      * Query the radio for the current network selection mode.
      *
      * Return values:
@@ -4018,6 +4025,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;
     }
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/PhoneSubInfoController.java b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
index a65bfeb..073e242 100644
--- a/src/java/com/android/internal/telephony/PhoneSubInfoController.java
+++ b/src/java/com/android/internal/telephony/PhoneSubInfoController.java
@@ -33,6 +33,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.os.TelephonyServiceManager.ServiceRegisterer;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.PhoneNumberUtils;
@@ -64,6 +65,7 @@
     private AppOpsManager mAppOps;
     private FeatureFlags mFeatureFlags;
     private PackageManager mPackageManager;
+    private final int mVendorApiLevel;
 
     public PhoneSubInfoController(Context context) {
         this(context, new FeatureFlagsImpl());
@@ -80,6 +82,8 @@
         mContext = context;
         mPackageManager = context.getPackageManager();
         mFeatureFlags = featureFlags;
+        mVendorApiLevel = SystemProperties.getInt(
+                "ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
     }
 
     @Deprecated
@@ -799,7 +803,11 @@
 
         if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
                 || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
-                Binder.getCallingUserHandle())) {
+                Binder.getCallingUserHandle())
+                || mVendorApiLevel < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+            // Skip to check associated telephony feature,
+            // if compatibility change is not enabled for the current process or
+            // the SDK version of vendor partition is less than Android V.
             return;
         }
 
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index 86b4a60..b564961 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,8 +452,14 @@
         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));
+            }
         }
     }
 
@@ -456,24 +491,49 @@
         @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 */,
+                        mServiceCookies.get(mService).get()));
+            } else {
+                mRilHandler.sendMessage(mRilHandler.obtainMessage(EVENT_AIDL_PROXY_DEAD, mService,
+                        0 /* ignored arg2 */, mServiceCookies.get(mService).get()));
+            }
             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 +543,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 +574,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 +610,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 +645,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);
                 }
             }
         }
@@ -982,16 +1065,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);
+                }
             }
         }
     }
@@ -999,19 +1099,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 + ")");
@@ -1118,7 +1215,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;
             }
@@ -1140,12 +1237,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) {
@@ -1286,17 +1384,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)));
@@ -1307,6 +1394,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/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/SmsController.java b/src/java/com/android/internal/telephony/SmsController.java
index da1b07a..59184d8 100644
--- a/src/java/com/android/internal/telephony/SmsController.java
+++ b/src/java/com/android/internal/telephony/SmsController.java
@@ -38,6 +38,7 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.SystemProperties;
 import android.os.TelephonyServiceManager.ServiceRegisterer;
 import android.os.UserHandle;
 import android.provider.Telephony.Sms.Intents;
@@ -70,6 +71,8 @@
 
     private final Context mContext;
     private final PackageManager mPackageManager;
+    private final int mVendorApiLevel;
+
     @NonNull private final FeatureFlags mFlags;
 
     @VisibleForTesting
@@ -83,6 +86,9 @@
         if (smsServiceRegisterer.get() == null) {
             smsServiceRegisterer.register(this);
         }
+
+        mVendorApiLevel = SystemProperties.getInt(
+                "ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
     }
 
     private Phone getPhone(int subId) {
@@ -296,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) {
@@ -1224,7 +1231,11 @@
 
         if (!mFlags.enforceTelephonyFeatureMappingForPublicApis()
                 || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
-                Binder.getCallingUserHandle())) {
+                Binder.getCallingUserHandle())
+                || mVendorApiLevel < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+            // Skip to check associated telephony feature,
+            // if compatibility change is not enabled for the current process or
+            // the SDK version of vendor partition is less than Android V.
             return;
         }
 
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 7742ca1..4da1622 100644
--- a/src/java/com/android/internal/telephony/cat/CatService.java
+++ b/src/java/com/android/internal/telephony/cat/CatService.java
@@ -284,10 +284,12 @@
             CatLog.d(this, "Disposing CatService object");
             mIccRecords.unregisterForRecordsLoaded(this);
 
-            try {
-                mContext.unregisterReceiver(mSmsBroadcastReceiver);
-            } catch (IllegalArgumentException e) {
-                CatLog.e(this, "mSmsBroadcastReceiver: was not registered" + e);
+            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
diff --git a/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java b/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java
index 85d5a35..85413f5 100644
--- a/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java
+++ b/src/java/com/android/internal/telephony/configupdate/TelephonyConfigUpdateInstallReceiver.java
@@ -27,6 +27,8 @@
 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;
 
@@ -57,6 +59,7 @@
     private final Object mConfigParserLock = new Object();
     @GuardedBy("mConfigParserLock")
     private ConfigParser mConfigParser;
+    @NonNull private final ConfigUpdaterMetricsStats mConfigUpdaterMetricsStats;
 
 
     public static TelephonyConfigUpdateInstallReceiver sReceiverAdaptorInstance =
@@ -72,6 +75,7 @@
 
     public TelephonyConfigUpdateInstallReceiver() {
         super(UPDATE_DIR, NEW_CONFIG_CONTENT_PATH, UPDATE_METADATA_PATH, VERSION);
+        mConfigUpdaterMetricsStats = ConfigUpdaterMetricsStats.getOrCreateInstance();
     }
 
     /**
@@ -97,6 +101,8 @@
         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;
         }
 
@@ -109,12 +115,16 @@
             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;
                     }
                 }
@@ -149,8 +159,11 @@
                 int previousVersion = getInstance().mConfigParser.mVersion;
                 Log.d(TAG, "previous version is " + previousVersion + " | updated version is "
                         + updatedVersion);
+                mConfigUpdaterMetricsStats.setConfigVersion(updatedVersion);
                 if (updatedVersion <= previousVersion) {
                     Log.e(TAG, "updatedVersion is smaller than previousVersion");
+                    mConfigUpdaterMetricsStats.reportOemAndCarrierConfigError(
+                            SatelliteConstants.CONFIG_UPDATE_RESULT_INVALID_VERSION);
                     return;
                 }
             }
@@ -167,6 +180,8 @@
 
         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);
         }
     }
 
@@ -231,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) {
@@ -238,6 +255,8 @@
                 return new SatelliteConfigParser(data);
             default:
                 Log.e(TAG, "DOMAIN should be specified");
+                mConfigUpdaterMetricsStats.reportOemAndCarrierConfigError(
+                        SatelliteConstants.CONFIG_UPDATE_RESULT_INVALID_DOMAIN);
                 return null;
         }
     }
diff --git a/src/java/com/android/internal/telephony/data/AccessNetworksManager.java b/src/java/com/android/internal/telephony/data/AccessNetworksManager.java
index 3d3fbe9..1d720ac 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,7 +145,8 @@
      * 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;
         public QualifiedNetworks(@ApnType int apnType, @NonNull int[] qualifiedNetworks) {
@@ -198,11 +200,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) {
@@ -328,27 +331,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)
                         + "]");
diff --git a/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java b/src/java/com/android/internal/telephony/data/AutoDataSwitchController.java
index 343bb0b..10371ab 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 =
@@ -929,7 +940,7 @@
      * @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;
     }
 
     /**
@@ -1012,19 +1023,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. */
@@ -1085,8 +1097,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 6ad89ce..40039f2 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,
@@ -878,6 +886,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() {
diff --git a/src/java/com/android/internal/telephony/data/DataEvaluation.java b/src/java/com/android/internal/telephony/data/DataEvaluation.java
index a54884f..ae7023e 100644
--- a/src/java/com/android/internal/telephony/data/DataEvaluation.java
+++ b/src/java/com/android/internal/telephony/data/DataEvaluation.java
@@ -36,18 +36,23 @@
  */
 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
@@ -418,7 +423,7 @@
     public String toString() {
         StringBuilder evaluationStr = new StringBuilder();
         evaluationStr.append("Data evaluation: evaluation reason:" + mDataEvaluationReason + ", ");
-        if (mDataDisallowedReasons.size() > 0) {
+        if (!mDataDisallowedReasons.isEmpty()) {
             evaluationStr.append("Data disallowed reasons:");
             for (DataDisallowedReason reason : mDataDisallowedReasons) {
                 evaluationStr.append(" ").append(reason);
diff --git a/src/java/com/android/internal/telephony/data/DataNetwork.java b/src/java/com/android/internal/telephony/data/DataNetwork.java
index bd98403..9e84a77 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;
@@ -200,6 +201,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 +273,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,
@@ -552,6 +545,9 @@
     /** Metrics of per data network connection. */
     private final DataCallSessionStats mDataCallSessionStats;
 
+    /** Metrics of per data network validation. */
+    private final @NonNull 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
@@ -708,7 +704,7 @@
     private final boolean mIsSatellite;
 
     /** The reason that why setting up this data network is allowed. */
-    private @NonNull DataAllowedReason mDataAllowedReason;
+    private final @NonNull DataAllowedReason mDataAllowedReason;
 
     /**
      * PCO (Protocol Configuration Options) data received from the network. The first key is the
@@ -764,6 +760,12 @@
     private @Nullable AccessNetworksManagerCallback mAccessNetworksManagerCallback;
 
     /**
+     * PreciseDataConnectionState, the most recently notified. If it has never been notified, it is
+     * null.
+     */
+    private @Nullable PreciseDataConnectionState mPreciseDataConnectionState;
+
+    /**
      * The network bandwidth.
      */
     public static class NetworkBandwidth {
@@ -986,6 +988,7 @@
                 mDataNetworkControllerCallback);
         mDataConfigManager = mDataNetworkController.getDataConfigManager();
         mDataCallSessionStats = new DataCallSessionStats(mPhone);
+        mDataNetworkValidationStats = new DataNetworkValidationStats(mPhone);
         mDataNetworkCallback = callback;
         mDataProfile = dataProfile;
         if (dataProfile.getTrafficDescriptor() != null) {
@@ -1175,7 +1178,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,
@@ -1327,11 +1330,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:
@@ -1427,7 +1432,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;
@@ -1505,7 +1513,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
@@ -1553,12 +1561,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
@@ -1645,9 +1648,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));
@@ -1721,6 +1722,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;
             }
@@ -1736,6 +1743,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();
@@ -1919,6 +1927,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,
@@ -2019,7 +2030,7 @@
                 log("Successfully attached network request " + networkRequest);
             }
         }
-        if (failedList.size() > 0) {
+        if (!failedList.isEmpty()) {
             mDataNetworkCallback.invokeFromExecutor(() -> mDataNetworkCallback
                     .onAttachFailed(DataNetwork.this, failedList));
         }
@@ -2184,8 +2195,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.
@@ -2215,6 +2225,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) {
@@ -2443,8 +2454,8 @@
                 // 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);
                 }
@@ -2546,7 +2557,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
@@ -2648,7 +2658,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());
@@ -2660,7 +2670,7 @@
         }
 
         // Set DNS servers
-        if (response.getDnsAddresses().size() > 0) {
+        if (!response.getDnsAddresses().isEmpty()) {
             for (InetAddress dns : response.getDnsAddresses()) {
                 if (!dns.isAnyLocalAddress()) {
                     linkProperties.addDnsServer(dns);
@@ -2671,7 +2681,7 @@
         }
 
         // Set PCSCF
-        if (response.getPcscfAddresses().size() > 0) {
+        if (!response.getPcscfAddresses().isEmpty()) {
             for (InetAddress pcscf : response.getPcscfAddresses()) {
                 linkProperties.addPcscfServer(pcscf);
             }
@@ -2810,22 +2820,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");
                         }
                     }
                 }
@@ -2967,6 +2977,7 @@
                 mDataCallResponse = response;
                 if (response.getLinkStatus() != DataCallResponse.LINK_STATUS_INACTIVE) {
                     updateDataNetwork(response);
+                    notifyPreciseDataConnectionState();
                 } else {
                     log("onDataStateChanged: PDN inactive reported by "
                             + AccessNetworkConstants.transportTypeToString(mTransport)
@@ -3040,12 +3051,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;
         }
@@ -3404,6 +3415,7 @@
         return new PreciseDataConnectionState.Builder()
                 .setTransportType(mTransport)
                 .setId(mCid.get(mTransport))
+                .setNetworkAgentId(mNetworkAgent.getId())
                 .setState(getState())
                 .setApnSetting(mDataProfile.getApnSetting())
                 .setLinkProperties(mLinkProperties)
@@ -3417,11 +3429,22 @@
     /**
      * Send the precise data connection state to the listener of
      * {@link android.telephony.TelephonyCallback.PreciseDataConnectionStateListener}.
+     *
+     * 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);
+        }
     }
 
     /**
@@ -3511,6 +3534,8 @@
                 DataService.REQUEST_REASON_HANDOVER, mLinkProperties, mPduSessionId,
                 mNetworkSliceInfo, mHandoverDataProfile.getTrafficDescriptor(), true,
                 obtainMessage(EVENT_HANDOVER_RESPONSE, retryEntry));
+
+        mDataNetworkValidationStats.onHandoverAttempted();
     }
 
     /**
@@ -3558,6 +3583,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;
@@ -3681,7 +3708,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) {
@@ -3705,6 +3732,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");
     }
 
@@ -3733,8 +3765,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}
      */
@@ -3751,8 +3782,10 @@
                     + PreciseDataConnectionState.networkValidationStatusToString(
                     networkValidationStatus));
             mNetworkValidationStatus = networkValidationStatus;
-            notifyPreciseDataConnectionState();
         }
+
+        mDataNetworkValidationStats.onUpdateNetworkValidationState(
+                mNetworkValidationStatus, getDataNetworkType());
     }
 
     /**
@@ -3762,76 +3795,52 @@
      * @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";
-            case TEAR_DOWN_REASON_DATA_NETWORK_TRANSPORT_NOT_ALLOWED:
-                return "TEAR_DOWN_REASON_DATA_NETWORK_TRANSPORT_NOT_ALLOWED";
-            default:
-                return "UNKNOWN(" + 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 + ")";
+        };
     }
 
     /**
@@ -3841,64 +3850,41 @@
      * @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 + ")";
-        }
+        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
diff --git a/src/java/com/android/internal/telephony/data/DataNetworkController.java b/src/java/com/android/internal/telephony/data/DataNetworkController.java
index 2725295..3104d9e 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;
@@ -244,17 +246,22 @@
             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);
@@ -393,12 +400,6 @@
     private @NonNull SlidingWindowEventCounter mSetupDataCallWwanFailureCounter;
 
     /**
-     * {@code true} if {@link #tearDownAllDataNetworks(int)} was invoked and waiting for all
-     * networks torn down.
-     */
-    private boolean mPendingTearDownAllNetworks = false;
-
-    /**
      * The capabilities of the latest released IMS request. To detect back to back release/request
      * IMS network.
      */
@@ -1621,7 +1622,7 @@
         }
 
         // Check if there are pending tear down all networks request.
-        if (mPendingTearDownAllNetworks) {
+        if (mPhone.getServiceStateTracker().isPendingRadioPowerOffAfterDataOff()) {
             evaluation.addDataDisallowedReason(DataDisallowedReason.PENDING_TEAR_DOWN_ALL);
         }
 
@@ -1760,7 +1761,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
@@ -1775,13 +1777,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;
     }
 
     /**
@@ -1801,8 +1805,7 @@
 
             if (!TextUtils.isEmpty(subscriberId)) {
                 builder.setSubscriberIds(Set.of(subscriberId));
-                // Consider data usage calculation of only metered network.
-                // Emergency data usage is excluded.
+                // 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
@@ -1926,7 +1929,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");
@@ -2935,7 +2938,6 @@
         mDataNetworkList.remove(dataNetwork);
         trackSetupDataCallFailure(dataNetwork.getTransport(), cause);
         if (mAnyDataNetworkExisting && mDataNetworkList.isEmpty()) {
-            mPendingTearDownAllNetworks = false;
             mAnyDataNetworkExisting = false;
             mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
                     () -> callback.onAnyDataNetworkExistingChanged(mAnyDataNetworkExisting)));
@@ -3036,7 +3038,7 @@
         if (isEsimBootStrapProvisioningActivated()) {
             sendMessageDelayed(obtainMessage(EVENT_REEVALUATE_EXISTING_DATA_NETWORKS,
                     DataEvaluationReason.CHECK_DATA_USAGE),
-                    REEVALUATE_BOOTSTRAP_SIM_DATA_USAGE_MILLIS);
+                    mDataConfigManager.getReevaluateBootstrapSimDataUsageMillis());
         }
     }
 
@@ -3203,7 +3205,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");
@@ -3260,7 +3268,6 @@
 
         if (mAnyDataNetworkExisting && mDataNetworkList.isEmpty()) {
             log("All data networks disconnected now.");
-            mPendingTearDownAllNetworks = false;
             mAnyDataNetworkExisting = false;
             mDataNetworkControllerCallbacks.forEach(callback -> callback.invokeFromExecutor(
                     () -> callback.onAnyDataNetworkExistingChanged(mAnyDataNetworkExisting)));
@@ -3994,14 +4001,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);
diff --git a/src/java/com/android/internal/telephony/data/DataProfileManager.java b/src/java/com/android/internal/telephony/data/DataProfileManager.java
index 0da220b..2a2a792 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;
@@ -102,8 +103,9 @@
     /** The preferred data profile used for internet. */
     private @Nullable 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. */
+    private final @NonNull LruCache<Integer, DataProfile> mLastInternetDataProfiles =
+            new LruCache<>(256);
 
     /** Preferred data profile set id. */
     private int mPreferredDataProfileSetId = Telephony.Carriers.NO_APN_SET_ID;
@@ -452,9 +454,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
@@ -542,7 +546,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 +556,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) {
@@ -597,7 +603,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()
@@ -793,7 +799,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 +816,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 +841,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 +849,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;
         }
@@ -939,7 +944,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 +971,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 +985,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");
-                }
-            }
         }
     }
 
@@ -1106,10 +1095,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 +1209,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 1fdc182..7454d01 100644
--- a/src/java/com/android/internal/telephony/data/DataRetryManager.java
+++ b/src/java/com/android/internal/telephony/data/DataRetryManager.java
@@ -214,7 +214,7 @@
         public final @Nullable 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;
@@ -487,7 +487,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;
@@ -628,13 +628,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 + ")";
+            };
         }
 
         /**
@@ -1168,7 +1168,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 +1180,7 @@
                     .setDataProfile(dataProfile)
                     .setTransport(transport)
                     .build();
-            updateThrottleStatus(dataProfile, requestList, null,
+            throttleDataProfile(dataProfile, requestList, null,
                     ThrottleStatus.RETRY_TYPE_NEW_CONNECTION, transport,
                     dataSetupRetryEntry.retryElapsedTime);
             schedule(dataSetupRetryEntry);
@@ -1298,7 +1298,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 +1308,7 @@
                     .setDataNetwork(dataNetwork)
                     .build();
 
-            updateThrottleStatus(dataNetwork.getDataProfile(),
+            throttleDataProfile(dataNetwork.getDataProfile(),
                     dataNetwork.getAttachedNetworkRequestList(), dataNetwork,
                     ThrottleStatus.RETRY_TYPE_HANDOVER, targetTransport,
                     dataHandoverRetryEntry.retryElapsedTime);
@@ -1534,7 +1534,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 +1565,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 +1636,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 +1771,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)));
+        }
     }
 
     /**
@@ -1840,22 +1866,15 @@
      * @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 + ")";
-        }
+        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/DataStallRecoveryManager.java b/src/java/com/android/internal/telephony/data/DataStallRecoveryManager.java
index ee8890a..ec6b40f 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. */
@@ -224,12 +213,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 +466,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;
@@ -872,7 +861,7 @@
                     isFirstValidationAfterDoRecovery, timeDurationOfCurrentAction);
             logl(
                     "data stall: "
-                    + (isFirstDataStall == true ? "start" : isValid == false ? "in process" : "end")
+                    + (isFirstDataStall ? "start" : !isValid ? "in process" : "end")
                     + ", lastaction="
                     + recoveryActionToString(mLastAction)
                     + ", isRecovered="
@@ -965,18 +954,13 @@
      * @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 + ")";
-        }
+        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 + ")";
+        };
     }
 
     /**
@@ -1005,18 +989,13 @@
      * @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 + ")";
-        }
+        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 cca6deb..49e54e7 100644
--- a/src/java/com/android/internal/telephony/data/DataUtils.java
+++ b/src/java/com/android/internal/telephony/data/DataUtils.java
@@ -36,7 +36,6 @@
 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;
 
@@ -49,7 +48,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;
@@ -385,25 +383,6 @@
     }
 
     /**
-     * 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);
-    }
-
-    /**
      * Group the network requests into several list that contains the same network capabilities.
      *
      * @param networkRequestList The provided network requests.
diff --git a/src/java/com/android/internal/telephony/data/KeepaliveTracker.java b/src/java/com/android/internal/telephony/data/KeepaliveTracker.java
index f9139ec..0a01339 100644
--- a/src/java/com/android/internal/telephony/data/KeepaliveTracker.java
+++ b/src/java/com/android/internal/telephony/data/KeepaliveTracker.java
@@ -254,17 +254,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..b92a590 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;
@@ -178,29 +178,29 @@
     private long mRxBytesDeltaAcc;
 
     private ModemActivityInfo mLastModemActivityInfo = null;
-    private final TelephonyCallback mTelephonyCallback = new TelephonyCallbackImpl();
     private int mSignalStrengthDbm;
     private int mSignalLevel;
     private int mDataRat = TelephonyManager.NETWORK_TYPE_UNKNOWN;
     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 +270,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 +296,27 @@
                 }
             };
 
-    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);
+
+        ConnectivityManager cm = phone.getContext()
+                .getSystemService(ConnectivityManager.class);
+        if (cm != null) {
+            cm.registerDefaultNetworkCallback(mDefaultNetworkCallback, this);
+        }
         mTelephonyManager.registerTelephonyCallback(new HandlerExecutor(this),
-                mTelephonyCallback);
+                new TelephonyCallbackImpl());
         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 5c1d0e1..b7aa251 100644
--- a/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
+++ b/src/java/com/android/internal/telephony/data/PhoneSwitcher.java
@@ -32,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;
@@ -46,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;
@@ -68,6 +66,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;
@@ -84,6 +83,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;
@@ -103,11 +103,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).
  */
@@ -214,7 +215,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;
@@ -320,7 +320,8 @@
 
     private ConnectivityManager mConnectivityManager;
     private int mImsRegistrationTech = REGISTRATION_TECH_NONE;
-
+    @VisibleForTesting
+    public final SparseIntArray mImsRegistrationRadioTechMap = new SparseIntArray();
     private List<Set<CommandException.Error>> mCurrentDdsSwitchFailure;
 
     /** Data settings manager callback. Key is the phone id. */
@@ -390,6 +391,27 @@
             (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)-> {
+                try {
+                    ImsManager.getInstance(context, phoneId)
+                            .addRegistrationCallback(cb, executor);
+                } catch (ImsException e) {
+                    throw e;
+                }
+            };
+
+    /**
      * Method to get singleton instance.
      */
     public static PhoneSwitcher getInstance() {
@@ -432,8 +454,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;
@@ -494,6 +515,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) {
@@ -511,7 +540,10 @@
                             }});
                 phone.getDataSettingsManager().registerCallback(
                         mDataSettingsManagerCallbacks.get(phoneId));
-                registerForImsRadioTechChange(context, phoneId);
+
+                if (!mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
+                    registerForImsRadioTechChange(context, phoneId);
+                }
             }
             Set<CommandException.Error> ddsFailure = new HashSet<CommandException.Error>();
             mCurrentDdsSwitchFailure.add(ddsFailure);
@@ -721,7 +753,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()) {
@@ -733,7 +776,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()) {
@@ -885,6 +930,45 @@
         }
     }
 
+    /**
+     * 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);
+
+        if (subId == INVALID_SUBSCRIPTION_ID) {
+            // 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 false;
+        }
+
+        return true;
+    }
+
     private synchronized void onMultiSimConfigChanged(int activeModemCount) {
         // No change.
         if (mActiveModemCount == activeModemCount) return;
@@ -911,6 +995,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(),
@@ -933,7 +1025,10 @@
 
             Set<CommandException.Error> ddsFailure = new HashSet<CommandException.Error>();
             mCurrentDdsSwitchFailure.add(ddsFailure);
-            registerForImsRadioTechChange(mContext, phoneId);
+
+            if (!mFlags.changeMethodOfObtainingImsRegistrationRadioTech()) {
+                registerForImsRadioTechChange(mContext, phoneId);
+            }
         }
 
         mAutoDataSwitchController.onMultiSimConfigChanged(activeModemCount);
@@ -1100,10 +1195,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();
             }
@@ -1216,12 +1315,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);
     }
@@ -1492,10 +1589,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
@@ -1735,7 +1828,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);
@@ -1838,12 +1930,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..ebddbad 100644
--- a/src/java/com/android/internal/telephony/data/QosCallbackTracker.java
+++ b/src/java/com/android/internal/telephony/data/QosCallbackTracker.java
@@ -53,10 +53,14 @@
     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;
diff --git a/src/java/com/android/internal/telephony/data/TelephonyNetworkAgent.java b/src/java/com/android/internal/telephony/data/TelephonyNetworkAgent.java
index 5ed09e6..d90724e 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;
@@ -165,7 +163,6 @@
                 config, provider);
         register();
         mDataNetwork = dataNetwork;
-        mNetworkAgentConfig = config;
         mTelephonyNetworkAgentCallbacks.add(callback);
         mId = getNetwork().getNetId();
         mLogTag = "TNA-" + mId;
@@ -345,15 +342,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 877d7b8..a16cee6 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));
     }
@@ -291,13 +291,13 @@
     }
 
     @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, mFlags);
         boolean applied = mNetworkRequests.get(networkRequest)
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
index 6e1c8dd..e511e8f 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccCardController.java
@@ -31,8 +31,10 @@
 import android.content.pm.ComponentInfo;
 import android.content.pm.PackageManager;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
 import android.service.euicc.EuiccProfileInfo;
@@ -75,6 +77,7 @@
     private UiccController mUiccController;
     private FeatureFlags mFeatureFlags;
     private PackageManager mPackageManager;
+    private final int mVendorApiLevel;
 
     private static EuiccCardController sInstance;
 
@@ -143,6 +146,8 @@
         mEuiccController = euiccController;
         mFeatureFlags = featureFlags;
         mPackageManager = context.getPackageManager();
+        mVendorApiLevel = SystemProperties.getInt(
+                "ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
 
         if (isBootUp(mContext)) {
             mSimSlotStatusChangeReceiver = new SimSlotStatusChangedBroadcastReceiver();
@@ -1541,7 +1546,11 @@
 
         if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
                 || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
-                Binder.getCallingUserHandle())) {
+                Binder.getCallingUserHandle())
+                || mVendorApiLevel < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+            // Skip to check associated telephony feature,
+            // if compatibility change is not enabled for the current process or
+            // the SDK version of vendor partition is less than Android V.
             return;
         }
 
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index 18b4b14..1a5b99e 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -34,7 +34,9 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -124,6 +126,7 @@
     private final AppOpsManager mAppOpsManager;
     private final PackageManager mPackageManager;
     private final FeatureFlags mFeatureFlags;
+    private final int mVendorApiLevel;
 
     // These values should be set or updated upon 1) system boot, 2) EuiccService/LPA is bound to
     // the phone process, 3) values are updated remotely by server flags.
@@ -172,6 +175,8 @@
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mPackageManager = context.getPackageManager();
         mFeatureFlags = featureFlags;
+        mVendorApiLevel = SystemProperties.getInt(
+                "ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
     }
 
     /**
@@ -615,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 {
@@ -646,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;
             }
@@ -862,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,
@@ -1728,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) {
@@ -2181,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
@@ -2351,7 +2375,11 @@
 
         if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
                 || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
-                Binder.getCallingUserHandle())) {
+                Binder.getCallingUserHandle())
+                || mVendorApiLevel < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+            // Skip to check associated telephony feature,
+            // if compatibility change is not enabled for the current process or
+            // the SDK version of vendor partition is less than Android V.
             return;
         }
 
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/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
index a71355d..aee8867 100755
--- 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).
@@ -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 2eadf17..13fb826 100644
--- a/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/DataCallSessionStats.java
@@ -79,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);
     }
@@ -307,11 +308,13 @@
         copy.isNonDds = call.isNonDds;
         copy.isIwlanCrossSim = call.isIwlanCrossSim;
         copy.isNtn = call.isNtn;
+        copy.isSatelliteTransport = call.isSatelliteTransport;
         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();
@@ -335,6 +338,7 @@
         proto.isIwlanCrossSim = false;
         proto.isNtn = mSatelliteController != null
                 ? mSatelliteController.isInSatelliteModeForCarrierRoaming(mPhone) : false;
+        proto.isSatelliteTransport = isSatellite;
         return proto;
     }
 
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 61325316..f07cf60 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,9 @@
 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_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 +74,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 +95,9 @@
 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.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 +148,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 +168,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 +186,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 +237,11 @@
             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);
             Rlog.d(TAG, "registered");
         } else {
             Rlog.e(TAG, "could not get StatsManager, atoms not registered");
@@ -234,6 +249,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 +336,16 @@
                 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);
             default:
                 Rlog.e(TAG, String.format("unexpected atom ID %d", atomTag));
                 return StatsManager.PULL_SKIP;
@@ -516,7 +544,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 +573,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 +602,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 +966,72 @@
         }
     }
 
+    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;
+        }
+    }
+
     /** Registers a pulled atom ID {@code atomId}. */
     private void registerAtom(int atomId) {
         mStatsManager.setPullAtomCallback(atomId, /* metadata= */ null,
@@ -1110,7 +1205,8 @@
                 dataCallSession.handoverFailureRat,
                 dataCallSession.isNonDds,
                 dataCallSession.isIwlanCrossSim,
-                dataCallSession.isNtn);
+                dataCallSession.isNtn,
+                dataCallSession.isSatelliteTransport);
     }
 
     private static StatsEvent buildStatsEvent(ImsRegistrationStats stats) {
@@ -1414,6 +1510,69 @@
                 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);
+    }
+
     /** 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 979777b..a3e70c9 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,9 @@
 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.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 +177,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 +232,7 @@
             mMaxNumGbaEventStats = 5;
             mMaxOutgoingShortCodeSms = 5;
             mMaxNumSatelliteStats = 5;
+            mMaxNumDataNetworkValidation = 5;
         } else {
             mMaxNumVoiceCallSessions = 50;
             mMaxNumSms = 25;
@@ -247,6 +257,7 @@
             mMaxNumGbaEventStats = 10;
             mMaxOutgoingShortCodeSms = 10;
             mMaxNumSatelliteStats = 15;
+            mMaxNumDataNetworkValidation = 15;
         }
 
         mAtoms = loadAtomsFromFile();
@@ -807,6 +818,82 @@
         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);
+    }
+
     /**
      * Returns and clears the voice call sessions if last pulled longer than {@code
      * minIntervalMillis} ago, otherwise returns {@code null}.
@@ -1465,6 +1552,102 @@
         }
     }
 
+    /**
+     * 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;
+        }
+    }
+
     /** Saves {@link PersistAtoms} to a file in private storage immediately. */
     public synchronized void flushAtoms() {
         saveAtomsToFile(0);
@@ -1615,6 +1798,22 @@
             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);
 
             // out of caution, sanitize also the timestamps
             atoms.voiceCallRatUsagePullTimestampMillis =
@@ -1677,6 +1876,16 @@
                     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);
             return atoms;
         } catch (NoSuchFileException e) {
             Rlog.d(TAG, "PersistAtoms file not found");
@@ -2112,6 +2321,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.
@@ -2367,6 +2623,11 @@
         atoms.satelliteOutgoingDatagramPullTimestampMillis = currentTime;
         atoms.satelliteProvisionPullTimestampMillis = currentTime;
         atoms.satelliteSosMessageRecommenderPullTimestampMillis = currentTime;
+        atoms.dataNetworkValidationPullTimestampMillis = currentTime;
+        atoms.carrierRoamingSatelliteSessionPullTimestampMillis = currentTime;
+        atoms.carrierRoamingSatelliteControllerStatsPullTimestampMillis = currentTime;
+        atoms.satelliteEntitlementPullTimestampMillis = currentTime;
+        atoms.satelliteConfigUpdaterPullTimestampMillis = 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 a9aab58..2bf035e 100644
--- a/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
+++ b/src/java/com/android/internal/telephony/metrics/SatelliteStats.java
@@ -19,14 +19,22 @@
 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.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.ArrayList;
+import java.util.List;
+
 /** Tracks Satellite metrics for each phone */
 public class SatelliteStats {
     private static final String TAG = SatelliteStats.class.getSimpleName();
@@ -1206,6 +1214,691 @@
         }
     }
 
+    /**
+     * 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=" + mRsrpMedian
+                    + ", 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 + ")";
+        }
+    }
+
     /**  Create a new atom or update an existing atom for SatelliteController metrics */
     public synchronized void onSatelliteControllerMetrics(SatelliteControllerParams param) {
         SatelliteController proto = new SatelliteController();
@@ -1311,4 +2004,62 @@
         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);
+    }
 }
diff --git a/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java b/src/java/com/android/internal/telephony/metrics/ServiceStateStats.java
index d4830ae..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,6 +40,7 @@
 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;
@@ -62,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);
@@ -144,6 +149,10 @@
             addServiceStateAndSwitch(
                     prevState, now, getDataServiceSwitch(prevState.mServiceState, newState));
         }
+
+        if (dataRatMetricEnabled()) {
+            writeDataRatAtom(serviceState);
+        }
     }
 
     /** Updates the fold state of the device for the current service state. */
@@ -461,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/VoiceCallSessionStats.java b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
index 8be9e83..c4ad93a 100644
--- a/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
+++ b/src/java/com/android/internal/telephony/metrics/VoiceCallSessionStats.java
@@ -797,11 +797,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. */
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..f88069f
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteConstants.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;
+
+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 {}
+}
diff --git a/src/java/com/android/internal/telephony/satellite/SatelliteController.java b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
index 05051fa..8582eaa 100644
--- a/src/java/com/android/internal/telephony/satellite/SatelliteController.java
+++ b/src/java/com/android/internal/telephony/satellite/SatelliteController.java
@@ -117,6 +117,8 @@
 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;
@@ -220,6 +222,7 @@
     @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;
@@ -364,6 +367,10 @@
     @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}
@@ -473,6 +480,8 @@
         mControllerMetricsStats = ControllerMetricsStats.make(mContext);
         mProvisionMetricsStats = ProvisionMetricsStats.getOrCreateInstance();
         mSessionMetricsStats = SessionMetricsStats.getInstance();
+        mCarrierRoamingSatelliteControllerStats =
+                CarrierRoamingSatelliteControllerStats.getOrCreateInstance();
         mSubscriptionManagerService = SubscriptionManagerService.getInstance();
 
         // Create the DatagramController singleton,
@@ -3614,6 +3623,8 @@
                 if (!entitlementPlmnList.isEmpty()) {
                     mMergedPlmnListPerCarrier.put(subId, entitlementPlmnList);
                     logd("mMergedPlmnListPerCarrier is updated by Entitlement");
+                    mCarrierRoamingSatelliteControllerStats.reportConfigDataSource(
+                            SatelliteConstants.CONFIG_DATA_SOURCE_ENTITLEMENT);
                     return;
                 }
             }
@@ -3627,6 +3638,8 @@
                     logd("mMergedPlmnListPerCarrier is updated by ConfigUpdater : "
                             + String.join(",", plmnList));
                     mMergedPlmnListPerCarrier.put(subId, plmnList);
+                    mCarrierRoamingSatelliteControllerStats.reportConfigDataSource(
+                            SatelliteConstants.CONFIG_DATA_SOURCE_CONFIG_UPDATER);
                     return;
                 }
             }
@@ -3637,6 +3650,8 @@
                         mSatelliteServicesSupportedByCarriers.get(subId).keySet().stream().toList();
                 logd("mMergedPlmnListPerCarrier is updated by carrier config: "
                         + String.join(",", carrierPlmnList));
+                mCarrierRoamingSatelliteControllerStats.reportConfigDataSource(
+                        SatelliteConstants.CONFIG_DATA_SOURCE_CARRIER_CONFIG);
             } else {
                 carrierPlmnList = new ArrayList<>();
                 logd("Empty mMergedPlmnListPerCarrier");
@@ -4113,7 +4128,15 @@
             }
 
             synchronized (mSatelliteConnectedLock) {
+                CarrierRoamingSatelliteSessionStats sessionStats =
+                        mCarrierRoamingSatelliteSessionStatsMap.get(subId);
+
                 if (serviceState.isUsingNonTerrestrialNetwork()) {
+                    if (sessionStats != null && !mWasSatelliteConnectedViaCarrier.get(subId)) {
+                        // Log satellite connection start
+                        sessionStats.onConnectionStart();
+                    }
+
                     resetCarrierRoamingSatelliteModeParams(subId);
                     mWasSatelliteConnectedViaCarrier.put(subId, true);
 
@@ -4139,6 +4162,11 @@
                         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);
                 }
@@ -4163,6 +4191,27 @@
                 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 =
+                        new CarrierRoamingSatelliteSessionStats(mContext, phone.getCarrierId());
+                sessionStats.onSessionStart();
+                mCarrierRoamingSatelliteSessionStatsMap.put(subId, sessionStats);
+            } else if (lastNotifiedNtnMode && !currNtnMode) {
+                // Log satellite session end
+                CarrierRoamingSatelliteSessionStats sessionStats =
+                        mCarrierRoamingSatelliteSessionStatsMap.get(subId);
+                sessionStats.onSessionEnd();
+                mCarrierRoamingSatelliteSessionStatsMap.remove(subId);
             }
         }
     }
@@ -4523,6 +4572,8 @@
 
         notificationManager.notifyAsUser(NOTIFICATION_TAG, NOTIFICATION_ID,
                 notificationBuilder.build(), UserHandle.ALL);
+
+        mCarrierRoamingSatelliteControllerStats.reportCountOfSatelliteNotificationDisplayed();
     }
 
     private void resetCarrierRoamingSatelliteModeParams() {
@@ -4536,7 +4587,6 @@
     private void resetCarrierRoamingSatelliteModeParams(int subId) {
         if (!mFeatureFlags.carrierEnabledSatelliteFlag()) return;
 
-        logd("resetCarrierRoamingSatelliteModeParams subId:" + subId);
         synchronized (mSatelliteConnectedLock) {
             mLastSatelliteDisconnectedTimesMillis.put(subId, null);
             mSatModeCapabilitiesForCarrierRoaming.remove(subId);
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..78c5222
--- /dev/null
+++ b/src/java/com/android/internal/telephony/satellite/metrics/CarrierRoamingSatelliteSessionStats.java
@@ -0,0 +1,203 @@
+/*
+ * 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.content.Context;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.telephony.metrics.SatelliteStats;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class CarrierRoamingSatelliteSessionStats {
+    private static final String TAG = CarrierRoamingSatelliteSessionStats.class.getSimpleName();
+    private final Context mContext;
+
+    private int mCarrierId;
+    private boolean mIsNtnRoamingInHomeCountry;
+    private int mRsrpAvg;
+    private int mRsrpMedian;
+    private int mRssnrAvg;
+    private int mRssnrMedian;
+    private int mCountOfIncomingSms;
+    private int mCountOfOutgoingSms;
+    private int mCountOfIncomingMms;
+    private int mCountOfOutgoingMms;
+
+    private int mSessionStartTimeSec;
+    private List<Long> mConnectionStartTimeList;
+    private List<Long> mConnectionEndTimeList;
+
+    public CarrierRoamingSatelliteSessionStats(@NonNull Context context, int carrierId) {
+        logd("Create new CarrierRoamingSatelliteSessionStats.");
+        initializeParams();
+
+        mContext = context;
+        mCarrierId = carrierId;
+    }
+
+    /** Log carrier roaming satellite session start */
+    public void onSessionStart() {
+        mSessionStartTimeSec = getCurrentTimeInSec();
+        onConnectionStart();
+    }
+
+    /** Log carrier roaming satellite connection start */
+    public void onConnectionStart() {
+        mConnectionStartTimeList.add(getCurrentTime());
+    }
+
+    /** Log carrier roaming satellite session end */
+    public void onSessionEnd() {
+        onConnectionEnd();
+        reportMetrics();
+    }
+
+    /** Log carrier roaming satellite connection end */
+    public void onConnectionEnd() {
+        mConnectionEndTimeList.add(getCurrentTime());
+    }
+
+    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(getAvgConnectionGapSec(connectionGapList))
+                        .setSatelliteConnectionGapMaxSec(satelliteConnectionGapMaxSec)
+                        .setRsrpAvg(mRsrpAvg)
+                        .setRsrpMedian(mRsrpMedian)
+                        .setRssnrAvg(mRssnrAvg)
+                        .setRssnrMedian(mRssnrMedian)
+                        .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;
+        mRsrpAvg = 0;
+        mRsrpMedian = 0;
+        mRssnrAvg = 0;
+        mRssnrMedian = 0;
+        mCountOfIncomingSms = 0;
+        mCountOfOutgoingSms = 0;
+        mCountOfIncomingMms = 0;
+        mCountOfOutgoingMms = 0;
+
+        mSessionStartTimeSec = 0;
+        mConnectionStartTimeList = new ArrayList<>();
+        mConnectionEndTimeList = new ArrayList<>();
+    }
+
+    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 getAvgConnectionGapSec(@NonNull List<Integer> connectionGapList) {
+        if (connectionGapList.isEmpty()) {
+            return 0;
+        }
+
+        int totalConnectionGap = 0;
+        for (int gap : connectionGapList) {
+            totalConnectionGap += gap;
+        }
+
+        return (totalConnectionGap / connectionGapList.size());
+    }
+
+    private int getCurrentTimeInSec() {
+        return (int) (System.currentTimeMillis() / 1000);
+    }
+
+    private long getCurrentTime() {
+        return System.currentTimeMillis();
+    }
+
+    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/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/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/SubscriptionManagerService.java b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
index 2e0bd31..5d653c7 100644
--- a/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
+++ b/src/java/com/android/internal/telephony/subscription/SubscriptionManagerService.java
@@ -318,6 +318,9 @@
     @NonNull
     private final int[] mSimState;
 
+    /** Vendor API level from system property. */
+    private final int mVendorApiLevel;
+
     /**
      * {@code true} if a user profile can only see the SIMs associated with it, unless it possesses
      * no SIMs on the device.
@@ -472,6 +475,8 @@
         mEuiccManager = context.getSystemService(EuiccManager.class);
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
         mPackageManager = context.getPackageManager();
+        mVendorApiLevel = SystemProperties.getInt(
+                "ro.vendor.api_level", Build.VERSION.DEVICE_INITIAL_SDK_INT);
 
         mUiccController = UiccController.getInstance();
         mHandler = new Handler(looper);
@@ -938,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,
@@ -1578,6 +1587,10 @@
                             SubscriptionManager.RESTORE_SIM_SPECIFIC_SETTINGS_DATABASE_UPDATED)) {
                         logl("Sim specific settings changed the database.");
                         mSubscriptionDatabaseManager.reloadDatabaseSync();
+                        if (mFeatureFlags.backupAndRestoreForEnable2g()) {
+                            PhoneFactory.getPhone(phoneId)
+                                    .loadAllowedNetworksFromSubscriptionDatabase();
+                        }
                     }
                 }
 
@@ -3762,42 +3775,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 "";
         }
     }
 
@@ -3833,25 +3918,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 "";
     }
 
     /**
@@ -4199,6 +4310,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);
@@ -4532,7 +4647,11 @@
 
         if (!mFeatureFlags.enforceTelephonyFeatureMappingForPublicApis()
                 || !CompatChanges.isChangeEnabled(ENABLE_FEATURE_MAPPING, callingPackage,
-                Binder.getCallingUserHandle())) {
+                Binder.getCallingUserHandle())
+                || mVendorApiLevel < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+            // Skip to check associated telephony feature,
+            // if compatibility change is not enabled for the current process or
+            // the SDK version of vendor partition is less than Android V.
             return;
         }
 
@@ -4605,6 +4724,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);
     }
 
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/tests/telephonytests/Android.bp b/tests/telephonytests/Android.bp
index 8547581..a6b47cd 100644
--- a/tests/telephonytests/Android.bp
+++ b/tests/telephonytests/Android.bp
@@ -1,4 +1,5 @@
 package {
+    default_team: "trendy_team_fwk_telephony",
     // See: http://go/android-license-faq
     default_applicable_licenses: [
         "frameworks_opt_telephony_tests_telephonytests_license",
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/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
index bef8944..552b5a7 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java
@@ -362,6 +362,8 @@
                 return Context.ALARM_SERVICE;
             } else if (serviceClass == DevicePolicyManager.class) {
                 return Context.DEVICE_POLICY_SERVICE;
+            } else if (serviceClass == NotificationManager.class) {
+                return Context.NOTIFICATION_SERVICE;
             }
             return super.getSystemServiceName(serviceClass);
         }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
index 614846b..ba08f8b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/GsmCdmaPhoneTest.java
@@ -3019,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 d1ab64d..a6b2000 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/MultiSimSettingControllerTest.java
@@ -911,6 +911,28 @@
         // 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(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+
+        // 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
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 1af4a76..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;
@@ -230,7 +231,12 @@
     @Test
     @SmallTest
     @EnableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
-    public void testGetNai_EnabledEnforceTelephonyFeatureMappingForPublicApis() {
+    public void testGetNai_EnabledEnforceTelephonyFeatureMappingForPublicApis() throws Exception {
+        // Replace field to set SDK version of vendor partition to Android V
+        int vendorApiLevel = Build.VERSION_CODES.VANILLA_ICE_CREAM;
+        replaceInstance(PhoneSubInfoController.class, "mVendorApiLevel",
+                mPhoneSubInfoControllerUT, vendorApiLevel);
+
         // FeatureFlags enabled, System has required feature
         doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
         doReturn(true).when(mPm).hasSystemFeature(
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..fd99ad0 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";
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/SmsControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java
index f8d1bec..06dbd0b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SmsControllerTest.java
@@ -31,6 +31,7 @@
 
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.telephony.TelephonyManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -295,11 +296,15 @@
 
     @Test
     @EnableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
-    public void sendTextForSubscriberTestEnabledTelephonyFeature() {
+    public void sendTextForSubscriberTestEnabledTelephonyFeature() throws Exception {
         int subId = 1;
         doReturn(true).when(mSubscriptionManager)
                 .isSubscriptionAssociatedWithUser(eq(subId), any());
 
+        // Replace field to set SDK version of vendor partition to Android V
+        int vendorApiLevel = Build.VERSION_CODES.VANILLA_ICE_CREAM;
+        replaceInstance(SmsController.class, "mVendorApiLevel", mSmsControllerUT, vendorApiLevel);
+
         // Feature enabled, device does not have required telephony feature.
         doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
         doReturn(false).when(mPackageManager).hasSystemFeature(
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/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/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/DataNetworkControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkControllerTest.java
index d11b730..28e129c 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;
@@ -854,6 +855,8 @@
         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
@@ -3482,9 +3485,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
@@ -4151,6 +4151,41 @@
     }
 
     @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);
@@ -5360,4 +5395,23 @@
         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());
+    }
 }
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 19502f5..d5a52ea 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataNetworkTest.java
@@ -256,12 +256,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();
@@ -654,6 +660,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
@@ -1233,6 +1252,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();
@@ -2245,6 +2287,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(
@@ -2332,6 +2419,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));
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 3d6b4f4..30ce46f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataProfileManagerTest.java
@@ -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);
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 c413f83..acfa16d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/data/DataRetryManagerTest.java
@@ -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);
     }
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 8420acf..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);
@@ -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 {
@@ -1756,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() {
@@ -1862,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);
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 57ae9ed..e374551 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
@@ -48,6 +48,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
+import android.os.Build;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.UserManager;
@@ -230,6 +231,7 @@
                 Settings.Global.EUICC_PROVISIONED, 0);
         Settings.Global.putInt(mContext.getContentResolver(),
                 Settings.Global.EUICC_PROVISIONED, 0);
+        setHasManageDevicePolicyManagedSubscriptionsPermission(false);
     }
 
     @After
@@ -894,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);
@@ -909,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);
@@ -929,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);
     }
 
@@ -1605,7 +1683,12 @@
     @Test
     @EnableCompatChanges({EuiccManager.INACTIVE_PORT_AVAILABILITY_CHECK,
             TelephonyManager.ENABLE_FEATURE_MAPPING})
-    public void testIsSimPortAvailable_WithTelephonyFeatureMapping() {
+    public void testIsSimPortAvailable_WithTelephonyFeatureMapping() throws Exception {
+        // Replace field to set SDK version of vendor partition to Android V
+        int vendorApiLevel = Build.VERSION_CODES.VANILLA_ICE_CREAM;
+        replaceInstance(EuiccController.class, "mVendorApiLevel", (EuiccController) mController,
+                vendorApiLevel);
+
         // Feature flag enabled, device has required telephony feature.
         doReturn(true).when(mFeatureFlags).enforceTelephonyFeatureMappingForPublicApis();
         doReturn(true).when(mPackageManager).hasSystemFeature(
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 2e64c46..5ac21f8 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/DataCallSessionStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/DataCallSessionStatsTest.java
@@ -34,7 +34,8 @@
 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;
@@ -96,7 +97,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,
@@ -122,7 +123,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,
@@ -151,7 +152,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,
@@ -178,7 +179,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,
@@ -201,7 +202,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,
@@ -227,7 +228,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,
@@ -256,7 +257,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,
@@ -288,7 +289,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,
@@ -315,7 +316,7 @@
     public void testIsNtn() {
         when(mSatelliteController.isInSatelliteModeForCarrierRoaming(any())).thenReturn(true);
 
-        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS, false);
         mDataCallSessionStats.onSetupDataCallResponse(
                 mDefaultImsResponse,
                 TelephonyManager.NETWORK_TYPE_LTE,
@@ -338,7 +339,7 @@
         when(mSatelliteController.isInSatelliteModeForCarrierRoaming(any()))
                 .thenReturn(false);
 
-        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS);
+        mDataCallSessionStats.onSetupDataCall(ApnSetting.TYPE_IMS, false);
         mDataCallSessionStats.onSetupDataCallResponse(
                 mDefaultImsResponse,
                 TelephonyManager.NETWORK_TYPE_LTE,
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 3aad333..092522b 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/PersistAtomsStorageTest.java
@@ -62,10 +62,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;
 
@@ -73,9 +76,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;
@@ -90,7 +96,9 @@
 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.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;
@@ -286,6 +294,28 @@
     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 void makeTestData() {
         // MO call with SRVCC (LTE to UMTS)
         mCall1Proto = new VoiceCallSession();
@@ -1070,6 +1100,10 @@
 
         mOutgoingShortCodeSms = new OutgoingShortCodeSms[] {mOutgoingShortCodeSms1,
                 mOutgoingShortCodeSms2};
+
+        generateTestSatelliteData();
+
+        generateTestDataNetworkValidationsData();
     }
 
     private void generateTestSatelliteData() {
@@ -1254,6 +1288,161 @@
                 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};
+    }
+
+    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 {
@@ -1421,6 +1610,22 @@
         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;
         super.tearDown();
     }
 
@@ -4576,6 +4781,476 @@
     }
 
     @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
+    @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);
@@ -4660,6 +5335,16 @@
         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;
         FileOutputStream stream = new FileOutputStream(mTestFile);
         stream.write(PersistAtoms.toByteArray(atoms));
         stream.close();
@@ -4823,6 +5508,29 @@
         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 void assertAllPullTimestampEquals(long timestamp) {
         assertEquals(
                 timestamp,
@@ -5321,4 +6029,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 9a84224..a7f1e01 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/metrics/SatelliteStatsTest.java
@@ -24,7 +24,11 @@
 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.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;
@@ -296,4 +300,136 @@
                 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);
+    }
 }
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/SubscriptionManagerServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
index 9fee311..eb06ff1 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/subscription/SubscriptionManagerServiceTest.java
@@ -44,6 +44,7 @@
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_RCS_CONFIG1;
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_RCS_CONFIG2;
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_SATELLITE_ENTITLEMENT_PLMNS1;
+import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_SATELLITE_IS_NTN_DISABLED;
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_SUBSCRIPTION_INFO1;
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_SUBSCRIPTION_INFO2;
 import static com.android.internal.telephony.subscription.SubscriptionDatabaseManagerTest.FAKE_UUID1;
@@ -243,7 +244,6 @@
         doReturn(false).when(mFlags).enforceTelephonyFeatureMappingForPublicApis();
         doReturn(true).when(mPackageManager).hasSystemFeature(
                 eq(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
-
         logd("SubscriptionManagerServiceTest -Setup!");
     }
 
@@ -426,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();
@@ -484,7 +498,8 @@
 
     @Test
     @EnableCompatChanges({TelephonyManager.ENABLE_FEATURE_MAPPING})
-    public void testSetPhoneNumber_EnabledEnforceTelephonyFeatureMappingForPublicApis() {
+    public void testSetPhoneNumber_EnabledEnforceTelephonyFeatureMappingForPublicApis()
+            throws Exception {
         mContextFixture.addCallingOrSelfPermission(Manifest.permission.MODIFY_PHONE_STATE);
         mSubscriptionManagerServiceUT.addSubInfo(FAKE_ICCID1, FAKE_CARRIER_NAME1,
                 0, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
@@ -496,6 +511,11 @@
         // Grant carrier privilege
         setCarrierPrivilegesForSubId(true, 1);
 
+        // Replace field to set SDK version of vendor partition to Android V
+        int vendorApiLevel = Build.VERSION_CODES.VANILLA_ICE_CREAM;
+        replaceInstance(SubscriptionManagerService.class, "mVendorApiLevel",
+                mSubscriptionManagerServiceUT, vendorApiLevel);
+
         // Enabled FeatureFlags and ENABLE_FEATURE_MAPPING, telephony features are defined
         doReturn(true).when(mFlags).enforceTelephonyFeatureMappingForPublicApis();
         doReturn(true).when(mPackageManager).hasSystemFeature(
@@ -1788,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);
 
@@ -2483,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);
@@ -3134,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);
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;